feat: rewrite site as Next.js 14 portfolio with terminal aesthetic

Replaces Hugo site with a Next.js App Router (TypeScript) build.
Tailwind CSS custom terminal palette, Framer Motion scroll reveals,
JetBrains Mono font, scanline/vignette overlays, typing animation.
Sections: Hero, Skills, Projects, Contact. Content driven from data/resume.ts.
Deploy workflow updated for static export to gh-pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
emil28092005
2026-04-30 04:15:07 +03:00
co-authored by Claude Sonnet 4.6
parent a6ed78c2ca
commit 727996503e
21 changed files with 1392 additions and 445 deletions
+6 -12
View File
@@ -12,30 +12,24 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Hugo - name: Set up Node.js
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
- name: Set up Node.js for Tailwindcss
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
cache: 'npm'
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Build CSS asset - name: Build
run: npm run build:css run: npm run build
- name: Build Hugo site
run: hugo --minify
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v3
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./out
publish_branch: gh-pages publish_branch: gh-pages
keep_files: false keep_files: false
force_orphan: true force_orphan: true
commit_message: Deploy Hugo site commit_message: 'deploy: ${{ github.sha }}'
+138 -31
View File
@@ -1,49 +1,156 @@
# CLAUDE.md # CLAUDE.md — my-project
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. > описание проекта
## Project Overview ## Стек
Hugo static resume website for Emil Shanayev (DevOps & Backend Engineer), styled with Tailwind CSS 3.x and deployed to GitHub Pages via GitHub Actions. **Языки:** Go, TypeScript
**Frontend:** React, TypeScript
**Инфраструктура:** Docker, Kubernetes, GitHub Actions, Jenkins
**Стиль кода:** Минимальные зависимости, явные типы (Go type hints + TS strict mode)
## Commands ---
```bash ## Принципы работы агента
# Local dev server (watches for changes)
hugo server
# Compile Tailwind CSS only - При неоднозначности в задаче — спрашивай, не угадывай и не двигайся вперёд.
npm run build:css - Предпочитай простейшее рабочее решение. Не создавай абстракций без необходимости.
- Не трогай код, файлы и конфиги вне скоупа текущей задачи.
- Верифицируй результат: запускай тесты, проверяй edge cases до завершения задачи.
- Не ломай существующие тесты ради реализации новой функциональности.
# Full production build (CSS + Hugo minified) ---
npm run build
## Запрещено
- Не изменять `.env`, `docker-compose.yml`, `k8s/prod/*` без явного разрешения.
- Не делать `git push --force` ни при каких обстоятельствах.
- Не деплоить в production без явного подтверждения пользователя.
- Не удалять Kubernetes namespaces, PersistentVolumes или secrets без явной команды.
- Не изменять pipeline-конфиги (Jenkinsfile, .github/workflows/*) без обсуждения.
---
## Точки остановки (обязательный checkpoint)
Агент обязан остановиться и явно спросить разрешение перед:
- Любыми изменениями в `k8s/prod/*` или `k8s/staging/*`
- Удалением Docker-образов, volumes или сетей
- Изменением RBAC-правил или ServiceAccount'ов
- Ротацией или созданием новых secrets/credentials
- Изменением resource limits/requests на production-сервисах
- Любой операцией, которую нельзя откатить за < 5 минут
---
## Структура проекта
```
.
├── cmd/ # точки входа (main.go)
├── internal/ # приватный код приложения
├── pkg/ # публичные пакеты
├── frontend/ # React-приложение (TypeScript)
│ ├── src/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── pages/
│ └── package.json
├── k8s/
│ ├── base/ # базовые манифесты
│ ├── staging/ # overlays для staging
│ └── prod/ # overlays для production ← только с разрешения
├── .github/
│ └── workflows/ # GitHub Actions pipelines
├── Dockerfile
├── docker-compose.yml ← не изменять без разрешения
└── .env ← не изменять без разрешения
``` ```
CSS must be compiled before Hugo can use it — `npm run build` handles this in the correct order. ---
## Architecture ## Соглашения по коду (Go)
**Hugo + PostCSS pipeline:** - Стиль: стандартный `gofmt` / `goimports`
- Линтер: `golangci-lint` — запускать перед коммитом
- Ошибки: всегда оборачивать через `fmt.Errorf("...: %w", err)`
- Логирование: использовать структурированный логгер (slog / zap), не `fmt.Println`
- Конфигурация: через переменные окружения, не хардкодить значения
- Зависимости: минимальные; новую зависимость — только с обоснованием
- `assets/css/tailwind.css` — Tailwind entry point (source); processed by PostCSS → `assets/css/main.css` (generated, not committed) ---
- `tailwind.config.js` — content glob scans `**/*.{html,js,ts,jsx,md,go,rs}`; dark mode via `class`; custom color palette under `primary`, `secondary`, `dark`, `bg`
- `layouts/` — Hugo HTML templates (currently `_default/` only)
- `content/` — Markdown pages; `_index.md` is the homepage resume content (written in Russian)
- `config.toml` — Hugo site config (author, description, menu entries)
**CI/CD (`.github/workflows/`):** ## Соглашения по коду (React / TypeScript)
- `ci.yml` — runs on PRs to `main`: installs deps, lints Markdown (`markdownlint`) and generated HTML (`htmlhint`), then builds - TypeScript strict mode обязателен (`"strict": true` в tsconfig)
- `deploy.yml` — runs on push to `main`: builds site and pushes `public/` to `gh-pages` branch via `peaceiris/actions-gh-pages` - Компоненты: только функциональные, без class components
- Именование: компоненты — `PascalCase`, хуки — `useCamelCase`, файлы — `kebab-case.tsx`
- Стейт: локальный через `useState`/`useReducer`; глобальный — только при явной необходимости
- Пропсы: всегда типизировать через `interface`, не `type` (для расширяемости)
- Эффекты: не использовать `useEffect` для получения данных — использовать React Query / SWR
- Линтер: ESLint + `eslint-plugin-react-hooks` — запускать перед коммитом
- Форматтер: Prettier с конфигом из репозитория — не менять настройки
- Тесты: Vitest + React Testing Library; не тестировать детали реализации, только поведение
- Никаких `any` типов — использовать `unknown` + type guard при необходимости
**Utility directories:** ---
- `core/user/` — user identity and preference notes consumed by AI tooling ## CI/CD соглашения
- `templates/` — blank identity/preference templates
- `.pi/` — pi-agent settings (repo URL reference)
## Style Preferences ### GitHub Actions
- Каждый PR запускает: lint → test → build → docker build
- Образы тегируются: `<registry>/<app>:<git-sha>`
- Merge в `main` → автодеплой на staging
- 2-space indentation ### Jenkins
- Descriptive variable names, no trailing whitespace - Production-деплой только через Jenkins pipeline с ручным approve-шагом
- TypeScript preferred for any scripting; REST over GraphQL; PostgreSQL/Redis for data - Rollback через `helm rollback` или `kubectl rollout undo`
---
## Работа с Docker
- Base image: `gcr.io/distroless/static-debian12` или `alpine:3.x` — не `latest`
- Multi-stage build обязателен для Go-бинарей
- Никаких секретов в ENV инструкциях Dockerfile (использовать BuildKit secrets или runtime env)
- Образы сканировать через `trivy` в CI перед пушем
---
## Работа с Kubernetes
- Все манифесты через Kustomize (не сырой kubectl apply в production)
- Resource requests/limits — обязательны для каждого контейнера
- Liveness/readiness probes — обязательны
- PodDisruptionBudget — обязателен для stateful сервисов
- Никаких `latest` тегов в манифестах
---
## Тестирование
- Unit-тесты: `go test ./...`
- Интеграционные тесты: `go test -tags=integration ./...`
- Покрытие Go: цель ≥ 70% для `internal/`
- Frontend тесты: `pnpm test` (Vitest) — запускать из `frontend/`
- Перед завершением задачи: убедиться, что все тесты проходят
---
## Соглашения по коммитам (Conventional Commits)
```
feat: новая функциональность
fix: исправление бага
chore: рутинные задачи (зависимости, конфиги)
refactor: рефакторинг без изменения поведения
test: добавление/изменение тестов
ci: изменения в CI/CD пайплайнах
docs: документация
```
Примеры:
- `feat(auth): add JWT validation middleware`
- `fix(deploy): correct resource limits for worker pod`
- `ci: add trivy scan step to GitHub Actions`
+107
View File
@@ -0,0 +1,107 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--bg: #0a0a0a;
--card: #0f0f0f;
--amber: #f59e0b;
--green: #22c55e;
--text: #e4e4e7;
--muted: #71717a;
--border: #1c1c1e;
--border2: #27272a;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
scroll-padding-top: 4rem;
}
body {
background-color: var(--bg);
color: var(--text);
font-family: 'JetBrains Mono', ui-monospace, monospace;
-webkit-font-smoothing: antialiased;
min-height: 100vh;
}
/* Scanline overlay */
body::before {
content: '';
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 3px,
rgba(0, 0, 0, 0.018) 3px,
rgba(0, 0, 0, 0.018) 4px
);
pointer-events: none;
z-index: 9999;
}
/* Subtle vignette */
body::after {
content: '';
position: fixed;
inset: 0;
background: radial-gradient(
ellipse at center,
transparent 60%,
rgba(0, 0, 0, 0.35) 100%
);
pointer-events: none;
z-index: 9998;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 3px;
}
::-webkit-scrollbar-track {
background: var(--bg);
}
::-webkit-scrollbar-thumb {
background: var(--border2);
}
::-webkit-scrollbar-thumb:hover {
background: var(--muted);
}
/* Text selection */
::selection {
background: rgba(245, 158, 11, 0.25);
color: var(--text);
}
/* Cursor blink */
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.cursor-blink {
animation: blink 1.1s step-end infinite;
}
/* Fade up entrance */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-up {
animation: fadeUp 0.55s ease forwards;
}
/* Draw line */
@keyframes drawLine {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
+26
View File
@@ -0,0 +1,26 @@
import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: 'Emil Shanayev — DevOps & Backend Engineer',
description:
'Portfolio of Emil Shanayev — DevOps Engineer and Backend Developer specializing in Python/FastAPI, Kubernetes, Docker, and Linux infrastructure.',
keywords: ['DevOps', 'Backend', 'Python', 'Go', 'Kubernetes', 'Docker', 'FastAPI'],
authors: [{ name: 'Emil Shanayev' }],
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap"
rel="stylesheet"
/>
</head>
<body className="font-mono">{children}</body>
</html>
)
}
+19
View File
@@ -0,0 +1,19 @@
import { Nav } from '@/components/Nav'
import { Hero } from '@/components/Hero'
import { Skills } from '@/components/Skills'
import { Projects } from '@/components/Projects'
import { Contact } from '@/components/Contact'
export default function Home() {
return (
<>
<Nav />
<main className="max-w-3xl mx-auto px-5 sm:px-8 pb-40">
<Hero />
<Skills />
<Projects />
<Contact />
</main>
</>
)
}
+58
View File
@@ -0,0 +1,58 @@
import { resume } from '@/data/resume'
import { SectionLabel } from './SectionLabel'
import { Reveal } from './Reveal'
export function Contact() {
return (
<section id="contact" className="pb-16">
<Reveal>
<SectionLabel index="03" label="contact.sh" />
</Reveal>
<Reveal delay={0.05}>
<p className="text-[12px] text-terminal-muted mb-8 pl-1">
<span className="text-terminal-amber">$</span>
<span className="ml-2">echo &quot;Open to DevOps and Backend opportunities.&quot;</span>
</p>
</Reveal>
<div className="space-y-px">
{resume.contacts.map((c, i) => (
<Reveal key={c.label} delay={i * 0.06}>
<a
href={c.href}
target={c.href.startsWith('http') ? '_blank' : undefined}
rel="noreferrer"
className="group flex items-center gap-0 border border-transparent hover:border-terminal-border bg-transparent hover:bg-terminal-card transition-all duration-150 px-4 py-3.5"
>
<span className="w-24 text-[10px] text-terminal-muted tracking-widest uppercase flex-shrink-0">
{c.label}
</span>
<span className="text-terminal-border mx-4 group-hover:text-terminal-amber transition-colors"></span>
<span className="text-[12px] text-terminal-muted group-hover:text-terminal-text transition-colors font-mono">
{c.prefix && (
<span className="text-terminal-subtle">{c.prefix}</span>
)}
<span className="group-hover:text-terminal-amber transition-colors">
{c.handle.replace(c.prefix ?? '', '')}
</span>
</span>
</a>
</Reveal>
))}
</div>
{/* Footer */}
<Reveal delay={0.25}>
<div className="mt-20 pt-8 border-t border-terminal-border flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<span className="text-[10px] text-terminal-subtle tracking-widest">
EMIL SHANAYEV · {new Date().getFullYear()}
</span>
<span className="text-[10px] text-terminal-subtle opacity-50 tracking-wide">
built with Next.js · deployed to GitHub Pages
</span>
</div>
</Reveal>
</section>
)
}
+74
View File
@@ -0,0 +1,74 @@
import { resume } from '@/data/resume'
import { TypingText } from './TypingText'
export function Hero() {
const { name, role, bio, education, contacts } = resume
return (
<section className="pt-16 pb-24 sm:pt-20 sm:pb-32">
{/* Terminal chrome */}
<div className="mb-10 inline-flex items-center gap-2 text-[10px] text-terminal-muted tracking-widest">
<span className="w-2.5 h-2.5 rounded-full bg-[#ff5f57]" />
<span className="w-2.5 h-2.5 rounded-full bg-[#febc2e]" />
<span className="w-2.5 h-2.5 rounded-full bg-[#28c840]" />
<span className="ml-3 opacity-40">portfolio.sh</span>
</div>
{/* Shell prompt line */}
<p className="text-[11px] text-terminal-muted mb-8 tracking-wide">
<span className="text-terminal-amber">visitor</span>
<span className="opacity-40">@portfolio</span>
<span className="opacity-40">:~$</span>
<span className="ml-2 text-terminal-text">cat whoami.txt</span>
</p>
{/* Name */}
<h1 className="text-4xl sm:text-5xl md:text-6xl font-bold text-terminal-text tracking-tight leading-none mb-5">
{name}
</h1>
{/* Typing role */}
<p className="text-base sm:text-lg text-terminal-muted mb-10 h-7">
<span className="text-terminal-amber opacity-60 mr-2">&gt;</span>
<TypingText phrases={role} speed={80} deleteSpeed={40} pauseDuration={2000} />
</p>
{/* Bio */}
<p className="text-sm text-terminal-muted leading-relaxed max-w-2xl mb-10 border-l-2 border-terminal-border pl-4">
{bio}
</p>
{/* Education inline */}
<div className="mb-12 flex items-start gap-2 text-[11px]">
<span className="text-terminal-amber opacity-50 mt-0.5"></span>
<div>
<span className="text-terminal-text">{education.institution}</span>
<span className="text-terminal-muted mx-2">·</span>
<span className="text-terminal-muted">student</span>
</div>
</div>
{/* Contact links in hero */}
<div className="flex flex-wrap gap-3">
{contacts.map((c) => (
<a
key={c.label}
href={c.href}
target={c.href.startsWith('http') ? '_blank' : undefined}
rel="noreferrer"
className="group inline-flex items-center gap-2 px-4 py-2 text-[11px] text-terminal-muted border border-terminal-border hover:border-terminal-amber hover:text-terminal-text transition-all duration-200 tracking-wide"
>
<span className="text-terminal-amber opacity-0 group-hover:opacity-100 transition-opacity"></span>
<span className="uppercase tracking-widest text-[10px]">{c.label}</span>
<span className="opacity-40">/</span>
<span>{c.handle}</span>
</a>
))}
</div>
{/* Divider */}
<div className="mt-16 h-px w-full bg-gradient-to-r from-terminal-amber/20 via-terminal-border to-transparent" />
</section>
)
}
+49
View File
@@ -0,0 +1,49 @@
'use client'
import { useEffect, useState } from 'react'
import { resume } from '@/data/resume'
const links = [
{ label: 'skills', href: '#skills' },
{ label: 'projects', href: '#projects' },
{ label: 'contact', href: '#contact' },
]
export function Nav() {
const [scrolled, setScrolled] = useState(false)
useEffect(() => {
const handler = () => setScrolled(window.scrollY > 40)
window.addEventListener('scroll', handler, { passive: true })
return () => window.removeEventListener('scroll', handler)
}, [])
return (
<header
className={`sticky top-0 z-50 transition-all duration-300 ${
scrolled
? 'bg-terminal-bg/90 backdrop-blur-sm border-b border-terminal-border'
: 'bg-transparent'
}`}
>
<nav className="max-w-3xl mx-auto px-5 sm:px-8 h-14 flex items-center justify-between">
<span className="text-[11px] text-terminal-muted tracking-widest">
<span className="text-terminal-amber">~/</span>
{resume.handle}
</span>
<div className="flex items-center gap-6">
{links.map((l) => (
<a
key={l.href}
href={l.href}
className="text-[11px] text-terminal-muted hover:text-terminal-text transition-colors tracking-wider"
>
{l.label}
</a>
))}
</div>
</nav>
</header>
)
}
+80
View File
@@ -0,0 +1,80 @@
import { resume } from '@/data/resume'
import { SectionLabel } from './SectionLabel'
import { Reveal } from './Reveal'
const statusConfig = {
deployed: { label: 'deployed', color: 'text-terminal-green' },
shipped: { label: 'shipped', color: 'text-terminal-amber' },
wip: { label: 'wip', color: 'text-terminal-muted' },
} as const
export function Projects() {
return (
<section id="projects" className="pb-24">
<Reveal>
<SectionLabel index="02" label="projects.log" />
</Reveal>
<div className="space-y-4">
{resume.projects.map((project, i) => {
const status = statusConfig[project.status]
return (
<Reveal key={project.id} delay={i * 0.07}>
<article className="group relative border border-terminal-border hover:border-terminal-amber/40 bg-terminal-card transition-all duration-200 p-5 sm:p-6">
{/* Top row */}
<div className="flex items-start justify-between gap-4 mb-3">
<div className="flex items-center gap-3">
<span className="text-[10px] text-terminal-amber opacity-40 font-mono">
[{project.id}]
</span>
<h3 className="text-sm font-semibold text-terminal-text group-hover:text-white transition-colors">
{project.title}
</h3>
</div>
<div className="flex items-center gap-3 flex-shrink-0">
<span className={`text-[10px] tracking-widest ${status.color}`}>
{status.label}
</span>
{project.href && (
<a
href={project.href}
target="_blank"
rel="noreferrer"
className="text-[10px] text-terminal-muted hover:text-terminal-amber transition-colors tracking-wider"
>
</a>
)}
</div>
</div>
{/* Description */}
<p className="text-[12px] text-terminal-muted leading-relaxed mb-4 pl-9">
{project.description}
</p>
{/* Tech stack */}
<div className="pl-9 flex flex-wrap gap-1.5">
{project.tech.map((t) => (
<span
key={t}
className="px-2 py-px text-[10px] text-terminal-subtle border border-terminal-border/60 tracking-wide"
>
{t}
</span>
))}
</div>
{/* Left accent line on hover */}
<div className="absolute left-0 top-0 bottom-0 w-0.5 bg-terminal-amber scale-y-0 group-hover:scale-y-100 transition-transform duration-200 origin-top" />
</article>
</Reveal>
)
})}
</div>
</section>
)
}
+27
View File
@@ -0,0 +1,27 @@
'use client'
import { motion, useInView } from 'framer-motion'
import { useRef } from 'react'
interface RevealProps {
children: React.ReactNode
delay?: number
className?: string
}
export function Reveal({ children, delay = 0, className }: RevealProps) {
const ref = useRef<HTMLDivElement>(null)
const inView = useInView(ref, { once: true, margin: '-60px' })
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 16 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.55, delay, ease: [0.22, 1, 0.36, 1] }}
className={className}
>
{children}
</motion.div>
)
}
+19
View File
@@ -0,0 +1,19 @@
interface Props {
index: string
label: string
id?: string
}
export function SectionLabel({ index, label, id }: Props) {
return (
<div id={id} className="flex items-center gap-4 mb-10">
<span className="text-[10px] text-terminal-amber font-mono tracking-widest opacity-60">
{index}
</span>
<span className="text-[11px] text-terminal-muted font-mono tracking-[0.22em] uppercase">
{label}
</span>
<div className="flex-1 h-px bg-terminal-border" />
</div>
)
}
+45
View File
@@ -0,0 +1,45 @@
import { resume } from '@/data/resume'
import { SectionLabel } from './SectionLabel'
import { Reveal } from './Reveal'
export function Skills() {
return (
<section id="skills" className="pb-24">
<Reveal>
<SectionLabel index="01" label="skills.list" id="skills" />
</Reveal>
<div className="space-y-0">
{resume.skills.map((group, i) => (
<Reveal key={group.category} delay={i * 0.04}>
<div className="group flex flex-col sm:flex-row sm:items-start gap-3 sm:gap-0 py-3.5 border-b border-terminal-border/50 hover:border-terminal-border transition-colors">
{/* Category label */}
<div className="sm:w-36 flex-shrink-0 flex items-center gap-2">
<span className="text-[9px] text-terminal-amber opacity-40 w-3 text-right">
{String(i + 1).padStart(2, '0')}
</span>
<span className="text-[10px] text-terminal-muted tracking-[0.18em] uppercase pl-2">
{group.category}
</span>
</div>
{/* Skills */}
<div className="flex flex-wrap gap-2 sm:pl-4">
{group.items.map((skill) => (
<span
key={skill}
className="px-2.5 py-0.5 text-[11px] text-terminal-muted border border-terminal-border hover:border-terminal-amber/50 hover:text-terminal-text transition-all duration-150 cursor-default"
>
{skill}
</span>
))}
</div>
</div>
</Reveal>
))}
</div>
</section>
)
}
+63
View File
@@ -0,0 +1,63 @@
'use client'
import { useEffect, useState } from 'react'
interface Props {
phrases: readonly string[]
speed?: number
deleteSpeed?: number
pauseDuration?: number
}
export function TypingText({
phrases,
speed = 75,
deleteSpeed = 38,
pauseDuration = 2200,
}: Props) {
const [displayed, setDisplayed] = useState('')
const [phraseIdx, setPhraseIdx] = useState(0)
const [charIdx, setCharIdx] = useState(0)
const [deleting, setDeleting] = useState(false)
const [pausing, setPausing] = useState(false)
useEffect(() => {
if (pausing) return
const current = phrases[phraseIdx]
if (!deleting) {
if (charIdx < current.length) {
const t = setTimeout(() => setCharIdx((i) => i + 1), speed)
return () => clearTimeout(t)
}
if (phrases.length > 1) {
const t = setTimeout(() => {
setPausing(false)
setDeleting(true)
}, pauseDuration)
setPausing(true)
return () => clearTimeout(t)
}
return
}
if (charIdx > 0) {
const t = setTimeout(() => setCharIdx((i) => i - 1), deleteSpeed)
return () => clearTimeout(t)
}
setDeleting(false)
setPhraseIdx((i) => (i + 1) % phrases.length)
}, [charIdx, deleting, pausing, phraseIdx, phrases, speed, deleteSpeed, pauseDuration])
useEffect(() => {
setDisplayed(phrases[phraseIdx].slice(0, charIdx))
}, [charIdx, phraseIdx, phrases])
return (
<span>
{displayed}
<span className="cursor-blink text-terminal-amber"></span>
</span>
)
}
+112
View File
@@ -0,0 +1,112 @@
export const resume = {
name: 'Emil Shanayev',
handle: 'emil28092005',
role: ['DevOps Engineer', 'Backend Developer', 'Infrastructure Nerd'],
bio: 'Building reliable systems from first principles. Specializing in Python/FastAPI, container orchestration, and Linux infrastructure. Student at Innopolis University (DSAI program).',
location: 'Russia',
contacts: [
{
label: 'GitHub',
handle: 'emil28092005',
href: 'https://github.com/emil28092005',
prefix: 'github.com/',
},
{
label: 'Email',
handle: 'emil28092005@gmail.com',
href: 'mailto:emil28092005@gmail.com',
prefix: '',
},
{
label: 'Telegram',
handle: '@emilshanaty',
href: 'https://t.me/emilshanaty',
prefix: 't.me/',
},
],
skills: [
{
category: 'SYSTEM',
items: ['Linux', 'Bash', 'SSH', 'Nginx', 'systemd', 'Vim'],
},
{
category: 'LANGUAGES',
items: ['Python', 'Go', 'TypeScript', 'JavaScript', 'Java', 'C#'],
},
{
category: 'BACKEND',
items: ['FastAPI', 'asyncio', 'PostgreSQL', 'Redis', 'SQLite'],
},
{
category: 'CONTAINERS',
items: ['Docker', 'Compose', 'Kubernetes', 'Helm', 'Harbor'],
},
{
category: 'CI / CD',
items: ['GitHub Actions', 'Jenkins', 'GitLab CI'],
},
{
category: 'CLOUD',
items: ['Yandex Cloud', 'VPS', 'Server Admin'],
},
{
category: 'AI / LLM',
items: ['OpenAI API', 'Claude API', 'LangChain', 'Perplexity'],
},
{
category: 'TOOLING',
items: ['Git', 'Agile', 'Scrum', 'Jira'],
},
],
projects: [
{
id: '01',
title: 'FastAPI Async Platform',
description:
'High-performance async REST API built with FastAPI and PostgreSQL. Features Redis caching, Docker containerization, and Nginx reverse proxy on a Linux VPS.',
tech: ['Python', 'FastAPI', 'PostgreSQL', 'Redis', 'Docker', 'Nginx'],
href: 'https://github.com/emil28092005',
status: 'deployed' as const,
},
{
id: '02',
title: 'K8s Infrastructure',
description:
'Production-grade Kubernetes cluster with Helm chart management, Harbor private container registry, and full CI/CD automation via GitHub Actions.',
tech: ['Kubernetes', 'Helm', 'Harbor', 'GitHub Actions', 'Docker'],
href: 'https://github.com/emil28092005',
status: 'deployed' as const,
},
{
id: '03',
title: 'Jenkins CI/CD Pipeline',
description:
'End-to-end automated pipeline — testing, Docker image builds, registry push, and zero-downtime rolling deployments on Linux servers.',
tech: ['Jenkins', 'Docker', 'Bash', 'Linux'],
href: 'https://github.com/emil28092005',
status: 'shipped' as const,
},
{
id: '04',
title: 'LLM API Microservice',
description:
'Microservice integrating OpenAI and Claude APIs with async task queuing, per-user rate limiting, and response caching to minimize costs.',
tech: ['Python', 'FastAPI', 'Redis', 'OpenAI API', 'Claude API'],
href: 'https://github.com/emil28092005',
status: 'wip' as const,
},
],
education: {
institution: 'Innopolis University',
program: 'Data Structures and Artificial Intelligence (DSAI)',
note: 'Product Management elective — A grade',
},
} as const
export type Project = (typeof resume.projects)[number]
export type SkillGroup = (typeof resume.skills)[number]
export type Contact = (typeof resume.contacts)[number]
+5
View File
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
+13
View File
@@ -0,0 +1,13 @@
/** @type {import('next').NextConfig} */
const REPO_NAME = 'Emil_Shanayev_Resume'
const nextConfig = {
output: 'export',
basePath: `/${REPO_NAME}`,
assetPrefix: `/${REPO_NAME}`,
images: {
unoptimized: true,
},
}
export default nextConfig
+418 -371
View File
@@ -1,18 +1,26 @@
{ {
"name": "resume-website", "name": "emil-shanayev-portfolio",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "resume-website", "name": "emil-shanayev-portfolio",
"version": "1.0.0", "version": "1.0.0",
"dependencies": {
"framer-motion": "^11.0.8",
"next": "^14.2.5",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.19", "@types/node": "^20.14.0",
"autoprefixer": "^10.5.0", "@types/react": "^18.3.3",
"postcss": "^8.5.12", "@types/react-dom": "^18.3.0",
"postcss-cli": "^11.0.1", "autoprefixer": "^10.4.19",
"tailwindcss": "^3.4.4" "postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5"
} }
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {
@@ -67,6 +75,146 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@next/env": {
"version": "14.2.35",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz",
"integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ=="
},
"node_modules/@next/swc-darwin-arm64": {
"version": "14.2.33",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz",
"integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "14.2.33",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz",
"integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "14.2.33",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz",
"integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "14.2.33",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz",
"integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "14.2.33",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz",
"integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "14.2.33",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz",
"integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "14.2.33",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz",
"integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.2.33",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz",
"integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "14.2.33",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz",
"integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -105,57 +253,52 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@tailwindcss/typography": { "node_modules/@swc/counter": {
"version": "0.5.19", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
"dev": true, },
"license": "MIT", "node_modules/@swc/helpers": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
"integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
"dependencies": { "dependencies": {
"postcss-selector-parser": "6.0.10" "@swc/counter": "^0.1.3",
}, "tslib": "^2.4.0"
}
},
"node_modules/@types/node": {
"version": "20.19.39",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz",
"integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==",
"dev": true,
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"dev": true
},
"node_modules/@types/react": {
"version": "18.3.28",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true,
"peerDependencies": { "peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" "@types/react": "^18.0.0"
}
},
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/any-promise": { "node_modules/any-promise": {
@@ -296,6 +439,17 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
} }
}, },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"streamsearch": "^1.1.0"
},
"engines": {
"node": ">=10.16.0"
}
},
"node_modules/camelcase-css": { "node_modules/camelcase-css": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -310,7 +464,6 @@
"version": "1.0.30001791", "version": "1.0.30001791",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz",
"integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -365,40 +518,10 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/cliui": { "node_modules/client-only": {
"version": "8.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
}, },
"node_modules/commander": { "node_modules/commander": {
"version": "4.1.1", "version": "4.1.1",
@@ -423,15 +546,11 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/dependency-graph": { "node_modules/csstype": {
"version": "1.0.0", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true, "dev": true
"license": "MIT",
"engines": {
"node": ">=4"
}
}, },
"node_modules/didyoumean": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
@@ -454,13 +573,6 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/es-errors": { "node_modules/es-errors": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
@@ -548,19 +660,30 @@
"url": "https://github.com/sponsors/rawify" "url": "https://github.com/sponsors/rawify"
} }
}, },
"node_modules/fs-extra": { "node_modules/framer-motion": {
"version": "11.3.4", "version": "11.18.2",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
"integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==",
"dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.0", "motion-dom": "^11.18.1",
"jsonfile": "^6.0.1", "motion-utils": "^11.18.1",
"universalify": "^2.0.0" "tslib": "^2.4.0"
}, },
"engines": { "peerDependencies": {
"node": ">=14.14" "@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
} }
}, },
"node_modules/fsevents": { "node_modules/fsevents": {
@@ -588,16 +711,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -615,7 +728,6 @@
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/hasown": { "node_modules/hasown": {
@@ -670,16 +782,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-glob": { "node_modules/is-glob": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -713,18 +815,10 @@
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
}, },
"node_modules/jsonfile": { "node_modules/js-tokens": {
"version": "6.2.1", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
"dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
}, },
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "3.1.3", "version": "3.1.3",
@@ -746,6 +840,17 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -770,6 +875,19 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/motion-dom": {
"version": "11.18.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
"integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==",
"dependencies": {
"motion-utils": "^11.18.1"
}
},
"node_modules/motion-utils": {
"version": "11.18.1",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz",
"integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA=="
},
"node_modules/mz": { "node_modules/mz": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -786,7 +904,6 @@
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -801,6 +918,82 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/next": {
"version": "14.2.35",
"resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz",
"integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==",
"dependencies": {
"@next/env": "14.2.35",
"@swc/helpers": "0.5.5",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579",
"graceful-fs": "^4.2.11",
"postcss": "8.4.31",
"styled-jsx": "5.1.1"
},
"bin": {
"next": "dist/bin/next"
},
"engines": {
"node": ">=18.17.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "14.2.33",
"@next/swc-darwin-x64": "14.2.33",
"@next/swc-linux-arm64-gnu": "14.2.33",
"@next/swc-linux-arm64-musl": "14.2.33",
"@next/swc-linux-x64-gnu": "14.2.33",
"@next/swc-linux-x64-musl": "14.2.33",
"@next/swc-win32-arm64-msvc": "14.2.33",
"@next/swc-win32-ia32-msvc": "14.2.33",
"@next/swc-win32-x64-msvc": "14.2.33"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
"@playwright/test": "^1.41.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.3.0"
},
"peerDependenciesMeta": {
"@opentelemetry/api": {
"optional": true
},
"@playwright/test": {
"optional": true
},
"sass": {
"optional": true
}
}
},
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.38", "version": "2.0.38",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
@@ -849,7 +1042,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
@@ -914,75 +1106,6 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postcss-cli": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.1.tgz",
"integrity": "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.3.0",
"dependency-graph": "^1.0.0",
"fs-extra": "^11.0.0",
"picocolors": "^1.0.0",
"postcss-load-config": "^5.0.0",
"postcss-reporter": "^7.0.0",
"pretty-hrtime": "^1.0.3",
"read-cache": "^1.0.0",
"slash": "^5.0.0",
"tinyglobby": "^0.2.12",
"yargs": "^17.0.0"
},
"bin": {
"postcss": "index.js"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss": "^8.0.0"
}
},
"node_modules/postcss-cli/node_modules/postcss-load-config": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz",
"integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"lilconfig": "^3.1.1",
"yaml": "^2.4.2"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"jiti": ">=1.21.0",
"postcss": ">=8.0.9",
"tsx": "^4.8.1"
},
"peerDependenciesMeta": {
"jiti": {
"optional": true
},
"postcss": {
"optional": true
},
"tsx": {
"optional": true
}
}
},
"node_modules/postcss-import": { "node_modules/postcss-import": {
"version": "15.1.0", "version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
@@ -1096,33 +1219,6 @@
"postcss": "^8.2.14" "postcss": "^8.2.14"
} }
}, },
"node_modules/postcss-reporter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz",
"integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"picocolors": "^1.0.0",
"thenby": "^1.3.4"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/postcss-selector-parser": { "node_modules/postcss-selector-parser": {
"version": "6.1.2", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
@@ -1144,16 +1240,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/pretty-hrtime": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
"integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -1175,6 +1261,29 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -1198,16 +1307,6 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.12", "version": "1.22.12",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
@@ -1265,55 +1364,51 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"node_modules/slash": { "node_modules/scheduler": {
"version": "5.1.0", "version": "0.23.2",
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dev": true, "dependencies": {
"license": "MIT", "loose-envify": "^1.1.0"
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/string-width": { "node_modules/streamsearch": {
"version": "4.2.3", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": { "engines": {
"node": ">=8" "node": ">=10.0.0"
} }
}, },
"node_modules/strip-ansi": { "node_modules/styled-jsx": {
"version": "6.0.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
"dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "client-only": "0.0.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">= 12.0.0"
},
"peerDependencies": {
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"babel-plugin-macros": {
"optional": true
}
} }
}, },
"node_modules/sucrase": { "node_modules/sucrase": {
@@ -1390,13 +1485,6 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/thenby": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/thenby/-/thenby-1.4.1.tgz",
"integrity": "sha512-D5a/bO0KdalOE3q8MlrRmSxjbKZHT3MQmXkJP+r97Vw8MMwOZKOwUSEyTtK7eSMj2y0kyAjpYMRMZmmLw1FtNQ==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/thenify": { "node_modules/thenify": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@@ -1488,16 +1576,30 @@
"dev": true, "dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/universalify": { "node_modules/tslib": {
"version": "2.0.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "dev": true,
"license": "MIT", "bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": { "engines": {
"node": ">= 10.0.0" "node": ">=14.17"
} }
}, },
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -1536,40 +1638,14 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.8.3", "version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true,
"peer": true,
"bin": { "bin": {
"yaml": "bin.mjs" "yaml": "bin.mjs"
}, },
@@ -1579,35 +1655,6 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/eemeli" "url": "https://github.com/sponsors/eemeli"
} }
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
}
} }
} }
} }
+18 -8
View File
@@ -1,16 +1,26 @@
{ {
"name": "resume-website", "name": "emil-shanayev-portfolio",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build:css": "postcss assets/css/tailwind.css -o assets/css/main.css", "dev": "next dev",
"build": "npm run build:css && hugo --minify" "build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"framer-motion": "^11.0.8",
"next": "^14.2.5",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.19", "@types/node": "^20.14.0",
"autoprefixer": "^10.5.0", "@types/react": "^18.3.3",
"postcss": "^8.5.12", "@types/react-dom": "^18.3.0",
"postcss-cli": "^11.0.1", "autoprefixer": "^10.4.19",
"tailwindcss": "^3.4.4" "postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5"
} }
} }
+33 -23
View File
@@ -1,34 +1,44 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: 'class', content: [
content: ['./layouts/**/*.html', './content/**/*.md', './assets/**/*.{js,ts}'], './app/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./data/**/*.ts',
],
theme: { theme: {
extend: { extend: {
colors: { colors: {
primary: '#2563eb', terminal: {
secondary: '#112d44', bg: '#0a0a0a',
dark: { card: '#0f0f0f',
100: '#0f172a', border: '#1c1c1e',
200: '#1e293b', border2: '#27272a',
300: '#334155', amber: '#f59e0b',
400: '#475569', amberDim:'#78350f',
500: '#607d8b', green: '#22c55e',
600: '#304ffe', text: '#e4e4e7',
700: '#24368a', muted: '#71717a',
800: '#1d2951', subtle: '#3f3f46',
900: '#132743',
},
bg: {
default: '#ffffff',
primary: '#0f172a',
}, },
}, },
spacing: { fontFamily: {
'5': '1.25rem', mono: ['JetBrains Mono', 'ui-monospace', 'monospace'],
'7': '1.75rem', },
'9': '2.25rem', animation: {
blink: 'blink 1.1s step-end infinite',
'fade-up': 'fadeUp 0.6s ease forwards',
},
keyframes: {
blink: {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0' },
},
fadeUp: {
from: { opacity: '0', transform: 'translateY(12px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
}, },
}, },
}, },
plugins: [require('@tailwindcss/typography')], plugins: [],
} }
+59
View File
@@ -0,0 +1,59 @@
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./app/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./data/**/*.ts',
],
theme: {
extend: {
colors: {
terminal: {
bg: '#0a0a0a',
card: '#0f0f0f',
border: '#1c1c1e',
border2: '#27272a',
amber: '#f59e0b',
amberDim:'#78350f',
green: '#22c55e',
text: '#e4e4e7',
muted: '#71717a',
subtle: '#3f3f46',
},
},
fontFamily: {
mono: ['var(--font-jetbrains)', 'JetBrains Mono', 'ui-monospace', 'monospace'],
},
animation: {
blink: 'blink 1s step-end infinite',
'fade-up': 'fadeUp 0.6s ease forwards',
'draw-line': 'drawLine 0.8s ease forwards',
},
keyframes: {
blink: {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0' },
},
fadeUp: {
from: { opacity: '0', transform: 'translateY(12px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
drawLine: {
from: { width: '0' },
to: { width: '100%' },
},
},
typography: {
DEFAULT: {
css: {
fontFamily: 'var(--font-jetbrains), monospace',
},
},
},
},
},
plugins: [],
}
export default config
+23
View File
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./*"]
},
"target": "ES2017"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}