feat: update portfolio website

This commit is contained in:
emil28092005
2026-07-22 03:43:37 +03:00
parent 0a2d1941d5
commit 7bde4bad07
47 changed files with 1039 additions and 3226 deletions
-31
View File
@@ -1,31 +0,0 @@
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Verify output exists
run: |
if [ ! -d "out" ]; then
echo "out/ not found"
exit 1
fi
+33 -15
View File
@@ -1,35 +1,53 @@
name: Deploy to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
build-deploy:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Build
- name: Build static site
run: npm run build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./out
publish_branch: gh-pages
keep_files: false
force_orphan: true
commit_message: 'deploy: ${{ github.sha }}'
path: ./out
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+34 -16
View File
@@ -1,23 +1,41 @@
# Node dependencies
# Dependencies
node_modules/
# Build output
/public
/dist
/build
# Next.js
.next/
out/
# Hugo
*.log
*.patch
.hugo_build.lock
# Production
build/
dist/
# OS & editor artifacts
.DS_Store
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# TypeScript
*.tsbuildinfo
next-env.d.ts
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editors
.vscode/
.idea/
*.swp
*~
*.md~
*.swo
# Tailwind / PostCSS caches
/.cache/
assets/css/main.css
# Operating systems
.DS_Store
Thumbs.db
# Local tooling
.vercel/
.turbo/
-1
View File
@@ -1 +0,0 @@
{\"repoUrl\": \"file:///home/emil/Desktop/Coding/AI/UI_test\"}
-14
View File
@@ -1,14 +0,0 @@
# Task
Describe your task here.
## Goals
- Goal 1
- Goal 2
## Checklist
- [ ] Item 1
- [ ] Item 2
## Notes
(Update this as you work)
-14
View File
@@ -1,14 +0,0 @@
{
"name": "proceed",
"taskFile": ".ralph/proceed.md",
"iteration": 2,
"maxIterations": 50,
"itemsPerIteration": 0,
"reflectEvery": 0,
"reflectInstructions": "REFLECTION CHECKPOINT\n\nPause and reflect on your progress:\n1. What has been accomplished so far?\n2. What's working well?\n3. What's not working or blocking progress?\n4. Should the approach be adjusted?\n5. What are the next priorities?\n\nUpdate the task file with your reflection, then continue working.",
"active": false,
"status": "completed",
"startedAt": "2026-04-29T07:23:46.928Z",
"lastReflectionAt": 0,
"completedAt": "2026-04-29T07:38:48.762Z"
}
-271
View File
@@ -1,271 +0,0 @@
./content/_index.md
./package.json
./node_modules/@nodelib/fs.walk/package.json
./node_modules/@nodelib/fs.walk/README.md
./node_modules/@nodelib/fs.scandir/package.json
./node_modules/@nodelib/fs.scandir/README.md
./node_modules/@nodelib/fs.stat/package.json
./node_modules/@nodelib/fs.stat/README.md
./node_modules/@jridgewell/trace-mapping/package.json
./node_modules/@jridgewell/trace-mapping/README.md
./node_modules/@jridgewell/sourcemap-codec/package.json
./node_modules/@jridgewell/sourcemap-codec/README.md
./node_modules/@jridgewell/resolve-uri/package.json
./node_modules/@jridgewell/resolve-uri/README.md
./node_modules/@jridgewell/gen-mapping/package.json
./node_modules/@jridgewell/gen-mapping/README.md
./node_modules/postcss/package.json
./node_modules/postcss/README.md
./node_modules/@alloc/quick-lru/package.json
./node_modules/@alloc/quick-lru/readme.md
./node_modules/browserslist/package.json
./node_modules/browserslist/README.md
./node_modules/caniuse-lite/package.json
./node_modules/caniuse-lite/README.md
./node_modules/tailwindcss/lib/value-parser/README.md
./node_modules/tailwindcss/lib/postcss-plugins/nesting/README.md
./node_modules/tailwindcss/src/value-parser/README.md
./node_modules/tailwindcss/src/postcss-plugins/nesting/README.md
./node_modules/tailwindcss/package.json
./node_modules/tailwindcss/README.md
./node_modules/fraction.js/package.json
./node_modules/fraction.js/CHANGELOG.md
./node_modules/fraction.js/README.md
./node_modules/postcss-value-parser/package.json
./node_modules/postcss-value-parser/README.md
./node_modules/baseline-browser-mapping/package.json
./node_modules/baseline-browser-mapping/README.md
./node_modules/update-browserslist-db/package.json
./node_modules/update-browserslist-db/README.md
./node_modules/nanoid/async/package.json
./node_modules/nanoid/non-secure/package.json
./node_modules/nanoid/url-alphabet/package.json
./node_modules/nanoid/package.json
./node_modules/nanoid/README.md
./node_modules/source-map-js/package.json
./node_modules/source-map-js/README.md
./node_modules/arg/package.json
./node_modules/arg/LICENSE.md
./node_modules/arg/README.md
./node_modules/didyoumean/package.json
./node_modules/didyoumean/README.md
./node_modules/dlv/package.json
./node_modules/dlv/README.md
./node_modules/picocolors/package.json
./node_modules/picocolors/README.md
./node_modules/glob-parent/package.json
./node_modules/glob-parent/README.md
./node_modules/is-glob/package.json
./node_modules/is-glob/README.md
./node_modules/jiti/package.json
./node_modules/jiti/README.md
./node_modules/lilconfig/package.json
./node_modules/lilconfig/readme.md
./node_modules/micromatch/package.json
./node_modules/micromatch/README.md
./node_modules/postcss-import/package.json
./node_modules/postcss-import/README.md
./node_modules/object-hash/package.json
./node_modules/normalize-path/package.json
./node_modules/normalize-path/README.md
./node_modules/postcss-js/package.json
./node_modules/postcss-js/README.md
./node_modules/postcss-load-config/package.json
./node_modules/postcss-load-config/README.md
./node_modules/postcss-nested/package.json
./node_modules/postcss-nested/README.md
./node_modules/postcss-selector-parser/package.json
./node_modules/postcss-selector-parser/API.md
./node_modules/postcss-selector-parser/CHANGELOG.md
./node_modules/postcss-selector-parser/README.md
./node_modules/sucrase/package.json
./node_modules/sucrase/README.md
./node_modules/resolve/test/resolver/symlinked/package/package.json
./node_modules/resolve/test/resolver/browser_field/package.json
./node_modules/resolve/test/resolver/nested_symlinks/mylib/package.json
./node_modules/resolve/test/resolver/baz/package.json
./node_modules/resolve/test/resolver/dot_main/package.json
./node_modules/resolve/test/resolver/dot_slash_main/package.json
./node_modules/resolve/test/resolver/false_main/package.json
./node_modules/resolve/test/resolver/incorrect_main/package.json
./node_modules/resolve/test/resolver/multirepo/packages/package-a/package.json
./node_modules/resolve/test/resolver/multirepo/packages/package-b/package.json
./node_modules/resolve/test/resolver/multirepo/package.json
./node_modules/resolve/test/resolver/invalid_main/package.json
./node_modules/resolve/test/module_dir/zmodules/bbb/package.json
./node_modules/resolve/package.json
./node_modules/resolve/.claude/notes.md
./node_modules/resolve/.github/INCIDENT_RESPONSE_PROCESS.md
./node_modules/resolve/.github/THREAT_MODEL.md
./node_modules/resolve/SECURITY.md
./node_modules/anymatch/package.json
./node_modules/anymatch/README.md
./node_modules/electron-to-chromium/package.json
./node_modules/electron-to-chromium/README.md
./node_modules/node-releases/package.json
./node_modules/node-releases/README.md
./node_modules/braces/package.json
./node_modules/braces/README.md
./node_modules/readdirp/package.json
./node_modules/readdirp/README.md
./node_modules/is-binary-path/package.json
./node_modules/is-binary-path/readme.md
./node_modules/merge2/package.json
./node_modules/merge2/README.md
./node_modules/fill-range/package.json
./node_modules/fill-range/README.md
./node_modules/picomatch/package.json
./node_modules/picomatch/README.md
./node_modules/fastq/package.json
./node_modules/fastq/README.md
./node_modules/fastq/SECURITY.md
./node_modules/to-regex-range/package.json
./node_modules/to-regex-range/README.md
./node_modules/run-parallel/package.json
./node_modules/run-parallel/README.md
./node_modules/reusify/package.json
./node_modules/reusify/README.md
./node_modules/reusify/SECURITY.md
./node_modules/binary-extensions/package.json
./node_modules/binary-extensions/readme.md
./node_modules/is-extglob/package.json
./node_modules/is-extglob/README.md
./node_modules/read-cache/package.json
./node_modules/read-cache/README.md
./node_modules/camelcase-css/package.json
./node_modules/camelcase-css/README.md
./node_modules/cssesc/package.json
./node_modules/cssesc/README.md
./node_modules/pify/package.json
./node_modules/pify/readme.md
./node_modules/util-deprecate/package.json
./node_modules/util-deprecate/README.md
./node_modules/util-deprecate/History.md
./node_modules/es-errors/package.json
./node_modules/es-errors/CHANGELOG.md
./node_modules/es-errors/README.md
./node_modules/is-core-module/package.json
./node_modules/is-core-module/CHANGELOG.md
./node_modules/is-core-module/README.md
./node_modules/supports-preserve-symlinks-flag/package.json
./node_modules/supports-preserve-symlinks-flag/CHANGELOG.md
./node_modules/supports-preserve-symlinks-flag/README.md
./node_modules/path-parse/package.json
./node_modules/path-parse/README.md
./node_modules/hasown/package.json
./node_modules/hasown/CHANGELOG.md
./node_modules/hasown/README.md
./node_modules/function-bind/package.json
./node_modules/function-bind/CHANGELOG.md
./node_modules/function-bind/README.md
./node_modules/function-bind/.github/SECURITY.md
./node_modules/queue-microtask/package.json
./node_modules/queue-microtask/README.md
./node_modules/commander/package.json
./node_modules/commander/CHANGELOG.md
./node_modules/commander/Readme.md
./node_modules/lines-and-columns/package.json
./node_modules/lines-and-columns/README.md
./node_modules/mz/package.json
./node_modules/mz/README.md
./node_modules/mz/HISTORY.md
./node_modules/pirates/package.json
./node_modules/pirates/README.md
./node_modules/tinyglobby/node_modules/fdir/package.json
./node_modules/tinyglobby/node_modules/fdir/README.md
./node_modules/tinyglobby/node_modules/picomatch/package.json
./node_modules/tinyglobby/node_modules/picomatch/README.md
./node_modules/tinyglobby/package.json
./node_modules/tinyglobby/README.md
./node_modules/ts-interface-checker/package.json
./node_modules/ts-interface-checker/README.md
./node_modules/object-assign/package.json
./node_modules/object-assign/readme.md
./node_modules/any-promise/package.json
./node_modules/any-promise/README.md
./node_modules/thenify-all/package.json
./node_modules/thenify-all/README.md
./node_modules/thenify-all/History.md
./node_modules/thenify/package.json
./node_modules/thenify/History.md
./node_modules/thenify/README.md
./node_modules/is-number/package.json
./node_modules/is-number/README.md
./node_modules/escalade/package.json
./node_modules/escalade/readme.md
./node_modules/autoprefixer/lib/hacks/flex-spec.js
./node_modules/autoprefixer/package.json
./node_modules/autoprefixer/README.md
./node_modules/chokidar/node_modules/glob-parent/package.json
./node_modules/chokidar/node_modules/glob-parent/CHANGELOG.md
./node_modules/chokidar/node_modules/glob-parent/README.md
./node_modules/chokidar/package.json
./node_modules/chokidar/README.md
./node_modules/fast-glob/node_modules/glob-parent/package.json
./node_modules/fast-glob/node_modules/glob-parent/CHANGELOG.md
./node_modules/fast-glob/node_modules/glob-parent/README.md
./node_modules/fast-glob/package.json
./node_modules/fast-glob/README.md
./node_modules/postcss-reporter/package.json
./node_modules/postcss-reporter/README.md
./node_modules/fs-extra/package.json
./node_modules/fs-extra/README.md
./node_modules/pretty-hrtime/package.json
./node_modules/pretty-hrtime/README.md
./node_modules/slash/package.json
./node_modules/slash/readme.md
./node_modules/yargs/helpers/package.json
./node_modules/yargs/package.json
./node_modules/yargs/README.md
./node_modules/graceful-fs/package.json
./node_modules/graceful-fs/README.md
./node_modules/jsonfile/package.json
./node_modules/jsonfile/README.md
./node_modules/universalify/package.json
./node_modules/universalify/README.md
./node_modules/thenby/package.json
./node_modules/thenby/README.md
./node_modules/cliui/package.json
./node_modules/cliui/CHANGELOG.md
./node_modules/cliui/README.md
./node_modules/get-caller-file/package.json
./node_modules/get-caller-file/LICENSE.md
./node_modules/get-caller-file/README.md
./node_modules/require-directory/package.json
./node_modules/require-directory/README.markdown
./node_modules/string-width/package.json
./node_modules/string-width/readme.md
./node_modules/y18n/package.json
./node_modules/y18n/CHANGELOG.md
./node_modules/y18n/README.md
./node_modules/yargs-parser/package.json
./node_modules/yargs-parser/CHANGELOG.md
./node_modules/yargs-parser/README.md
./node_modules/strip-ansi/package.json
./node_modules/strip-ansi/readme.md
./node_modules/wrap-ansi/package.json
./node_modules/wrap-ansi/readme.md
./node_modules/emoji-regex/package.json
./node_modules/emoji-regex/README.md
./node_modules/is-fullwidth-code-point/package.json
./node_modules/is-fullwidth-code-point/readme.md
./node_modules/ansi-regex/package.json
./node_modules/ansi-regex/readme.md
./node_modules/ansi-styles/package.json
./node_modules/ansi-styles/readme.md
./node_modules/color-convert/package.json
./node_modules/color-convert/CHANGELOG.md
./node_modules/color-convert/README.md
./node_modules/color-name/package.json
./node_modules/color-name/README.md
./node_modules/yaml/browser/package.json
./node_modules/yaml/package.json
./node_modules/yaml/README.md
./node_modules/dependency-graph/specs/dep_graph_spec.js
./node_modules/dependency-graph/package.json
./node_modules/dependency-graph/CHANGELOG.md
./node_modules/dependency-graph/README.md
./node_modules/postcss-cli/node_modules/postcss-load-config/package.json
./node_modules/postcss-cli/node_modules/postcss-load-config/README.md
./node_modules/postcss-cli/package.json
./node_modules/postcss-cli/README.md
-17
View File
@@ -1,17 +0,0 @@
# IMPORTANT Rules
Always be kind. Prioritize user well-being.
# Code Quality Principles
Write clear, concise, and efficient code. Avoid unnecessary complexity.
# Coding Style Preferences
Use consistent 2-space indentation. Use descriptive variable names. Avoid trailing whitespace.
# Architecture Principles
Favor modular design. Consider scalability early. Prefer simplicity when possible.
# Development Workflow
Use Git for version control. Write descriptive commit messages. Use pull requests. Run tests before merging.
# Technical Preferences
Use TypeScript for type safety. Prefer REST over GraphQL for simplicity. Use PostgreSQL for relational data. Use Redis for caching. Use Docker for containerization.
-156
View File
@@ -1,156 +0,0 @@
# CLAUDE.md — my-project
> описание проекта
## Стек
**Языки:** Go, TypeScript
**Frontend:** React, TypeScript
**Инфраструктура:** Docker, Kubernetes, GitHub Actions, Jenkins
**Стиль кода:** Минимальные зависимости, явные типы (Go type hints + TS strict mode)
---
## Принципы работы агента
- При неоднозначности в задаче — спрашивай, не угадывай и не двигайся вперёд.
- Предпочитай простейшее рабочее решение. Не создавай абстракций без необходимости.
- Не трогай код, файлы и конфиги вне скоупа текущей задачи.
- Верифицируй результат: запускай тесты, проверяй edge cases до завершения задачи.
- Не ломай существующие тесты ради реализации новой функциональности.
---
## Запрещено
- Не изменять `.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 ← не изменять без разрешения
```
---
## Соглашения по коду (Go)
- Стиль: стандартный `gofmt` / `goimports`
- Линтер: `golangci-lint` — запускать перед коммитом
- Ошибки: всегда оборачивать через `fmt.Errorf("...: %w", err)`
- Логирование: использовать структурированный логгер (slog / zap), не `fmt.Println`
- Конфигурация: через переменные окружения, не хардкодить значения
- Зависимости: минимальные; новую зависимость — только с обоснованием
---
## Соглашения по коду (React / TypeScript)
- TypeScript strict mode обязателен (`"strict": true` в tsconfig)
- Компоненты: только функциональные, без 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 при необходимости
---
## CI/CD соглашения
### GitHub Actions
- Каждый PR запускает: lint → test → build → docker build
- Образы тегируются: `<registry>/<app>:<git-sha>`
- Merge в `main` → автодеплой на staging
### Jenkins
- Production-деплой только через Jenkins pipeline с ручным approve-шагом
- 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`
-1
View File
@@ -1 +0,0 @@
[['menu]\n [[menu.main]]\n name = "Resume"\n url = "/resume/', 'weight = 1\n [[menu.main]]\n name = "About"\n url = "/about/', 'weight = 2\n\n[params]\n author = "Emil Shanayev"\n description = "DevOps & Backend Engineer"\n keywords = ["DevOps', 'Backend', 'Go', 'Tailwind', 'Resume'], {'name': 'resume-website', 'private': True, 'scripts': {'build:css': 'postcss assets/css/tailwind.css -o assets/css/main.css', 'build': 'npm run build:css && hugo --minify', 'lint:md': "markdownlint '**/*.md' --config .markdownlint.json || echo 'No markdownlint config'", 'lint:html': "htmlhint 'public/**/*.html' || echo 'No HTML output yet'"}, 'devDependencies': {'autoprefixer': '^10.5.0', 'postcss': '^8.5.12', 'postcss-cli': '^11.0.1', 'tailwindcss': '^3.4.4', 'markdownlint-cli': '^0.35.0', 'htmlhint': '^1.2.0'}}]
+28
View File
@@ -0,0 +1,28 @@
# Emil Shanayev — Portfolio
Updated static portfolio based on the original `Emil_Shanayev_Resume` repository.
## Local development
```bash
npm install
npm run dev
```
Open `http://localhost:3000`.
## Production build
```bash
npm run build
```
The static export is written to `out/`.
## GitHub Pages
The project is preconfigured for:
`https://emil28092005.github.io/Emil_Shanayev_Resume/`
Push the project to the `main` branch, then select **Settings → Pages → Source → GitHub Actions**. The included workflow builds and deploys the static export automatically.
+81 -105
View File
@@ -1,107 +1,83 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Manrope:wght@400;500;600;700&display=swap');
: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); }
:root { --ink:#171914; --paper:#f2f0e9; --acid:#d7ff3f; --muted:#77796f; --line:#c8c7be; --dark:#20231c; }
* { box-sizing:border-box; }
html { scroll-behavior:smooth; }
body { margin:0; background:var(--paper); color:var(--ink); font-family:'Manrope',sans-serif; }
a { color:inherit; text-decoration:none; }
.shell { width:min(1180px, calc(100% - 48px)); margin-inline:auto; }
.nav { height:86px; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid var(--line); }
.mark { font:600 17px 'DM Mono',monospace; letter-spacing:-.04em; }
.mark span { color:var(--muted); font-weight:300; }
.nav nav { display:flex; gap:34px; font:400 12px 'DM Mono',monospace; text-transform:uppercase; letter-spacing:.08em; }
.nav nav a:hover { box-shadow:inset 0 -7px var(--acid); }
.hero { min-height:760px; padding:84px 0 70px; position:relative; border-bottom:1px solid var(--line); }
.hero-kicker { float:right; max-width:290px; font:400 11px/1.5 'DM Mono',monospace; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); display:flex; gap:10px; }
.pulse { width:8px; height:8px; margin-top:4px; background:#65b741; border-radius:50%; box-shadow:0 0 0 5px rgba(101,183,65,.12); flex:none; }
.eyebrow { font:400 11px 'DM Mono',monospace; text-transform:uppercase; letter-spacing:.15em; color:var(--muted); }
.hero h1 { font-size:clamp(70px,11.5vw,164px); line-height:.82; letter-spacing:-.085em; margin:108px 0 26px -8px; font-weight:600; }
.hero h2 { max-width:760px; font-size:clamp(28px,4.2vw,58px); line-height:1.03; letter-spacing:-.05em; margin:0 0 32px; font-weight:500; }
.hero-copy { width:min(620px,75%); margin-left:26%; color:#55584f; font-size:16px; line-height:1.75; }
.hero-actions { margin:42px 0 0 26%; display:flex; gap:10px; }
.button { padding:14px 18px; border:1px solid var(--ink); font:500 11px 'DM Mono',monospace; text-transform:uppercase; display:flex; gap:26px; justify-content:space-between; }
.button.primary { background:var(--ink); color:white; }
.button:hover { background:var(--acid); color:var(--ink); }
.hero-index { position:absolute; right:0; bottom:70px; font:300 11px 'DM Mono',monospace; color:var(--muted); }
.section-heading { display:flex; justify-content:space-between; align-items:flex-end; padding:23px 0; border-bottom:1px solid var(--ink); }
.section-heading p { margin:0; font:500 12px 'DM Mono',monospace; text-transform:uppercase; letter-spacing:.11em; }
.section-heading span { font:300 11px 'DM Mono',monospace; color:var(--muted); }
.capabilities,.work,.tools,.about { padding-top:100px; }
.cap-grid { display:grid; grid-template-columns:repeat(3,1fr); }
.cap-card { padding:42px 30px 36px 0; min-height:320px; border-right:1px solid var(--line); }
.cap-card + .cap-card { padding-left:30px; }
.cap-card:last-child { border-right:0; }
.cap-number { font:300 11px 'DM Mono',monospace; color:var(--muted); }
.cap-card h3 { font-size:24px; margin:54px 0 16px; letter-spacing:-.035em; }
.cap-card p { color:#65675f; font-size:14px; line-height:1.7; min-height:74px; }
.tag-list { display:flex; flex-wrap:wrap; gap:7px; margin-top:24px; }
.tag-list span { font:400 10px 'DM Mono',monospace; border:1px solid var(--line); padding:6px 8px; color:#55584f; }
.project { display:grid; grid-template-columns:160px minmax(0,1fr) 150px; gap:34px; padding:52px 0; border-bottom:1px solid var(--line); transition:padding .2s; }
.project:hover { padding-left:12px; }
.project-meta span { font:300 11px 'DM Mono',monospace; color:var(--muted); }
.project-meta p { font:400 10px/1.5 'DM Mono',monospace; text-transform:uppercase; color:var(--muted); margin-top:60px; }
.project-body h3 { margin:0 0 18px; font-size:clamp(30px,4vw,52px); letter-spacing:-.055em; font-weight:500; }
.project-body > p { max-width:720px; color:#5d6057; line-height:1.75; margin:0; }
.project-body ul { display:grid; grid-template-columns:1fr 1fr; padding:0; margin:26px 0 0; list-style:none; gap:9px 20px; }
.project-body li { font:400 11px 'DM Mono',monospace; }
.project-body li::before { content:'—'; color:var(--muted); margin-right:9px; }
.project-links { display:flex; align-items:flex-end; justify-content:flex-end; flex-direction:column; gap:10px; }
.project-links a { width:100%; display:flex; justify-content:space-between; padding:11px 0; border-bottom:1px solid var(--ink); font:400 10px 'DM Mono',monospace; text-transform:uppercase; }
.project-links a:hover { background:var(--acid); padding-inline:8px; }
.tool-grid { display:grid; grid-template-columns:1fr 1fr; }
.tool-grid a { min-height:130px; border-bottom:1px solid var(--line); padding:26px 0; display:flex; justify-content:space-between; gap:30px; }
.tool-grid a:nth-child(odd) { padding-right:30px; border-right:1px solid var(--line); }
.tool-grid a:nth-child(even) { padding-left:30px; }
.tool-grid strong { font-size:18px; }
.tool-grid p { color:var(--muted); font-size:12px; margin:9px 0 0; }
.tool-grid a:hover strong { box-shadow:inset 0 -8px var(--acid); }
.education { display:grid; grid-template-columns:160px 1fr 1fr; gap:34px; padding:48px 0 100px; }
.education > span { font:300 11px 'DM Mono',monospace; color:var(--muted); }
.education h3 { margin:0 0 8px; font-size:22px; }
.education p { margin:0; color:var(--muted); line-height:1.6; }
.education-note { font-size:13px; }
.footer { background:var(--dark); color:#f3f1e9; width:100%; max-width:none; padding:90px max(24px,calc((100% - 1180px)/2)) 32px; margin-top:30px; }
.footer .eyebrow { color:#a4a69c; }
.footer h2 { font-size:clamp(45px,7vw,96px); line-height:.98; letter-spacing:-.065em; font-weight:500; margin:48px 0 80px; }
.footer h2 em { color:var(--acid); font-style:normal; }
.contact-grid { border-top:1px solid #484b42; }
.contact-grid a { display:grid; grid-template-columns:120px 1fr 30px; padding:22px 0; border-bottom:1px solid #484b42; align-items:center; }
.contact-grid span { font:400 10px 'DM Mono',monospace; text-transform:uppercase; color:#92958a; }
.contact-grid strong { font-weight:400; }
.contact-grid a:hover strong { color:var(--acid); }
.footer-bottom { display:flex; justify-content:space-between; padding-top:60px; color:#787b71; font:300 9px 'DM Mono',monospace; text-transform:uppercase; }
@media (max-width:760px) {
.shell { width:min(100% - 28px,1180px); }
.nav { height:70px; }.nav nav { gap:14px; }.nav nav a:nth-child(2){display:none}
.hero { min-height:auto; padding:55px 0 60px; }.hero-kicker { float:none; margin-bottom:70px; }.hero h1 { margin-top:0; font-size:19vw; }.hero h2 { font-size:36px; }
.hero-copy,.hero-actions { width:100%; margin-left:0; }.hero-copy { margin-top:30px; }.hero-actions { flex-direction:column; }.hero-index { display:none; }
.section-heading span { display:none; }.capabilities,.work,.tools,.about { padding-top:70px; }
.cap-grid { grid-template-columns:1fr; }.cap-card,.cap-card + .cap-card { border-right:0; border-bottom:1px solid var(--line); padding:34px 0; min-height:0; }.cap-card h3 { margin-top:28px; }.cap-card p { min-height:0; }
.project { grid-template-columns:1fr; gap:20px; padding:38px 0; }.project:hover { padding-left:0; }.project-meta { display:flex; justify-content:space-between; }.project-meta p { margin:0; }.project-body ul { grid-template-columns:1fr; }.project-links { align-items:flex-start; }.project-links a { width:150px; }
.tool-grid { grid-template-columns:1fr; }.tool-grid a:nth-child(n) { border-right:0; padding:24px 0; }
.education { grid-template-columns:1fr; gap:20px; padding-bottom:70px; }.contact-grid a { grid-template-columns:80px 1fr 20px; }.contact-grid strong { font-size:12px; overflow-wrap:anywhere; }.footer-bottom { gap:20px; }.footer-bottom span:last-child { text-align:right; }
}
+4 -14
View File
@@ -2,25 +2,15 @@ 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'],
title: 'Emil Shanayev — AI Systems & Scientific Software',
description: 'Portfolio of Emil Shanayev: AI-native systems, scientific software, graphics and infrastructure.',
authors: [{ name: 'Emil Shanayev' }],
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
export default function RootLayout({ children }: Readonly<{ 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>
<body>{children}</body>
</html>
)
}
+110 -14
View File
@@ -1,19 +1,115 @@
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'
import { resume } from '@/data/resume'
const Arrow = () => <span aria-hidden="true"></span>
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>
</>
<main>
<header className="nav shell">
<a className="mark" href="#top" aria-label="Home">ES<span>/26</span></a>
<nav aria-label="Main navigation">
<a href="#work">Work</a>
<a href="#profile">Profile</a>
<a href="#contact">Contact</a>
</nav>
</header>
<section className="hero shell" id="top">
<div className="hero-kicker"><span className="pulse" /> {resume.availability}</div>
<p className="eyebrow">{resume.eyebrow}</p>
<h1>{resume.name}</h1>
<h2>{resume.role}</h2>
<p className="hero-copy">{resume.bio}</p>
<div className="hero-actions">
<a className="button primary" href="#work">Selected work <span></span></a>
<a className="button" href={resume.contacts[0].href} target="_blank" rel="noreferrer">GitHub <Arrow /></a>
</div>
<div className="hero-index" aria-hidden="true">0105</div>
</section>
<section className="capabilities shell" id="profile">
<div className="section-heading">
<p>Profile / capabilities</p>
<span>Three intersecting domains</span>
</div>
<div className="cap-grid">
{resume.capabilities.map((item) => (
<article className="cap-card" key={item.index}>
<span className="cap-number">{item.index}</span>
<h3>{item.title}</h3>
<p>{item.text}</p>
<div className="tag-list">{item.stack.map((tag) => <span key={tag}>{tag}</span>)}</div>
</article>
))}
</div>
</section>
<section className="work shell" id="work">
<div className="section-heading">
<p>Selected work</p>
<span>Built systems, not placeholder concepts</span>
</div>
<div className="project-list">
{resume.projects.map((project) => (
<article className="project" key={project.number}>
<div className="project-meta">
<span>{project.number}</span>
<p>{project.kind}</p>
</div>
<div className="project-body">
<h3>{project.name}</h3>
<p>{project.description}</p>
<ul>{project.highlights.map((highlight) => <li key={highlight}>{highlight}</li>)}</ul>
<div className="tag-list">{project.tech.map((tag) => <span key={tag}>{tag}</span>)}</div>
</div>
<div className="project-links">
<a href={project.href} target="_blank" rel="noreferrer">Repository <Arrow /></a>
{'secondaryHref' in project && project.secondaryHref && (
<a href={project.secondaryHref} target="_blank" rel="noreferrer">memwalk <Arrow /></a>
)}
</div>
</article>
))}
</div>
</section>
<section className="tools shell">
<div className="section-heading">
<p>Tools & experiments</p>
<span>Smaller utilities from active exploration</span>
</div>
<div className="tool-grid">
{resume.tools.map((tool) => (
<a href={tool.href} target="_blank" rel="noreferrer" key={tool.name}>
<div><strong>{tool.name}</strong><p>{tool.note}</p></div><Arrow />
</a>
))}
</div>
</section>
<section className="about shell">
<div className="section-heading">
<p>Education</p><span>Currently learning by building</span>
</div>
<div className="education">
<span>2024present</span>
<div><h3>{resume.education.institution}</h3><p>{resume.education.program}</p></div>
<p className="education-note">Interests: scientific computing, AI systems, computer graphics and distributed infrastructure.</p>
</div>
</section>
<footer className="footer shell" id="contact">
<p className="eyebrow">Have a difficult technical problem?</p>
<h2>Lets build something<br /><em>that actually works.</em></h2>
<div className="contact-grid">
{resume.contacts.map((contact) => (
<a href={contact.href} target={contact.href.startsWith('http') ? '_blank' : undefined} rel="noreferrer" key={contact.label}>
<span>{contact.label}</span><strong>{contact.value}</strong><Arrow />
</a>
))}
</div>
<div className="footer-bottom"><span>© 2026 Emil Shanayev</span><span>Designed as a static Next.js portfolio</span></div>
</footer>
</main>
)
}
-97
View File
@@ -1,97 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
body {
background-image: radial-gradient(rgb(30 41 59 / 0.45) 1px, transparent 1px);
background-size: 28px 28px;
}
}
@layer components {
/* Section header cards */
.resume-content h2 {
font-size: 0.68rem !important;
font-weight: 700 !important;
text-transform: uppercase;
letter-spacing: 0.16em;
color: rgb(96 165 250) !important;
margin-top: 3rem !important;
margin-bottom: 1.25rem !important;
padding: 0.55rem 1rem;
background: rgb(15 23 42 / 0.8);
border-left: 3px solid rgb(96 165 250);
border-radius: 0 0.375rem 0.375rem 0;
margin-left: -1rem;
box-shadow: -4px 0 24px rgb(96 165 250 / 0.08), inset 0 0 0 1px rgb(96 165 250 / 0.06);
}
.resume-content h3 {
font-size: 0.9rem !important;
font-weight: 600 !important;
color: rgb(226 232 240) !important;
margin-top: 1.25rem !important;
margin-bottom: 0.25rem !important;
}
.resume-content p {
color: rgb(148 163 184);
line-height: 1.75;
}
.resume-content li {
color: rgb(148 163 184);
}
.resume-content li::marker {
color: rgb(96 165 250) !important;
}
.resume-content strong {
color: rgb(226 232 240) !important;
font-weight: 600 !important;
}
.resume-content a {
color: rgb(96 165 250) !important;
text-decoration: none !important;
}
.resume-content a:hover {
text-decoration: underline !important;
}
.resume-content hr {
border-color: rgb(30 41 59 / 0.5) !important;
margin-top: 0.5rem !important;
margin-bottom: 0.5rem !important;
}
.skills-grid {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0.75rem 0 1.5rem;
}
.skills-grid span {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.72rem;
font-family: ui-monospace, monospace;
background: rgb(96 165 250 / 0.1);
color: rgb(96 165 250);
border: 1px solid rgb(96 165 250 / 0.25);
white-space: nowrap;
}
.resume-content code {
color: rgb(147 197 253) !important;
background: rgb(15 23 42) !important;
padding: 0.1em 0.4em !important;
border-radius: 0.25rem !important;
font-size: 0.85em !important;
}
}
-58
View File
@@ -1,58 +0,0 @@
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
@@ -1,74 +0,0 @@
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
@@ -1,49 +0,0 @@
'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
@@ -1,80 +0,0 @@
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
@@ -1,27 +0,0 @@
'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
@@ -1,19 +0,0 @@
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
@@ -1,45 +0,0 @@
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
@@ -1,63 +0,0 @@
'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>
)
}
-10
View File
@@ -1,10 +0,0 @@
baseURL = "https://emil28092005.github.io/Emil_Shanayev_Resume/"
languageCode = "ru"
title = "Emil Shanayev"
[params]
author = "Emil Shanayev"
description = "DevOps & Backend Engineer"
keywords = ["DevOps", "Backend", "Go", "Tailwind", "Resume"]
github = "https://github.com/emil28092005"
email = "emil28092005@gmail.com"
-58
View File
@@ -1,58 +0,0 @@
---
title: "Emil Shanayev"
---
## Обо мне
Молодой разработчик с глубокими знаниями в backend‑разработке и DevOps‑инженерии. Специализируюсь на Python/FastAPI, контейнеризации (Docker, Kubernetes) и Linux‑системах. Имею практический опыт проектирования архитектур, работы с CI/CD пайплайнами и управления инфраструктурой. Предпочитаю командную работу, обладаю лидерскими качествами и умею находить творческие решения сложных задач.
---
## Ключевые навыки
### Backend & Web Development
{{< skills "Python · FastAPI · asyncio" "Go · микросервисы" "JavaScript / TypeScript" "PostgreSQL · Redis · SQLite" "Tailwind CSS" >}}
### DevOps & Infrastructure
{{< skills "Docker · Compose" "Kubernetes · Helm" "GitHub Actions · Jenkins" "Linux · Bash" "Harbor" >}}
### Дополнительные компетенции
{{< skills "Git · GitHub Actions" "Agile · Scrum" "Java · C#" "VPS · сервер‑администрирование" >}}
---
## Образование
**Университет Иннополис** – Программа Data Structures and Artificial Intelligence (DSAI)
*2 года обучения*, факультатив *Product Management* отличная оценка
---
## Профессиональный опыт
**Самостоятельные проекты**
- Разработка backend‑приложений на **Python/FastAPI** с асинхронным программированием
- Контейнеризация в **Docker**, оркестрация в **Kubernetes**
- Настройка **CI/CD**‑пайплайнов с **GitHub Actions** и **Jenkins**
- Администрирование **Linux‑серверов** (Debian, Ubuntu)
**Командный опыт**
- Вузовские учебные проекты – лидерство в небольших командах
- Agile‑процессы, Scrum‑методики
---
## Дополнительно
- **Обучение**: постоянное изучение документаций, YouTube‑курсов, Perplexity AI
- **Открытый исходный код**: работа с инструментами DevOps‑сферы
- **Командная работа**: наставничество, менторинг
---
*Продолжаю развиваться в направлении DevOps и облачных технологий, активно экспериментирую с новыми подходами к инфраструктуре и автоматизации.*
-5
View File
@@ -1,5 +0,0 @@
---
title: "DevOps Engineer"
tags: ["DevOps", "Backend", "Go"]
draft: true
---
@@ -1 +0,0 @@
# Резюме _DevOps-инженер _ Backend-разработчик
-6
View File
@@ -1,6 +0,0 @@
GitHub indicates standard conventions: tailwind.css -> main.css (built). Hugo builds site to .???
I need to examine the assets/css folder:
</parameter>
</function>
</tool_call>
-1
View File
@@ -1 +0,0 @@
# Identity Template
-1
View File
@@ -1 +0,0 @@
# Preferences Template
-6
View File
@@ -1,6 +0,0 @@
- IMPORTANT Rules: Always be kind. Prioritize user well-being.
- Code Quality Principles: Write clear, concise, and efficient code. Avoid unnecessary complexity.
- Coding Style Preferences: 2-space indentation, descriptive variable names.
- Architecture Principles: Modular design, scalability focus.
- Development Workflow: Git + PRs + descriptive commits + tests.
- Technical Preferences: TypeScript, REST, PostgreSQL, Redis, Docker.
+65 -86
View File
@@ -1,112 +1,91 @@
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).',
eyebrow: 'AI systems · scientific software · infrastructure',
role: 'AI Systems & Scientific Software Developer',
bio: 'I build technical products at the intersection of scientific computing, AI-native software and systems engineering — from molecular similarity tools to a Vulkan renderer and production-oriented backend infrastructure.',
location: 'Russia',
availability: 'Open to internships, research collaborations and engineering projects',
contacts: [
{ label: 'GitHub', value: 'emil28092005', href: 'https://github.com/emil28092005' },
{ label: 'Email', value: 'emil28092005@gmail.com', href: 'mailto:emil28092005@gmail.com' },
{ label: 'Telegram', value: '@emilshanaty', href: 'https://t.me/emilshanaty' },
],
capabilities: [
{
label: 'GitHub',
handle: 'emil28092005',
href: 'https://github.com/emil28092005',
prefix: 'github.com/',
index: '01',
title: 'Scientific software',
text: 'Molecular data processing, cheminformatics workflows, numerical methods and reproducible command-line tools.',
stack: ['Python', 'RDKit', 'ChEMBL', 'NumPy', 'Flask'],
},
{
label: 'Email',
handle: 'emil28092005@gmail.com',
href: 'mailto:emil28092005@gmail.com',
prefix: '',
index: '02',
title: 'AI-native systems',
text: 'MCP integrations, local model tooling, persistent model state and human-in-the-loop workflows.',
stack: ['MCP', 'LLM APIs', 'PyTorch', 'SSM / Mamba', 'Computer Vision'],
},
{
label: 'Telegram',
handle: '@emilshanaty',
href: 'https://t.me/emilshanaty',
prefix: 't.me/',
index: '03',
title: 'Systems & infrastructure',
text: 'Performance-oriented engines, backend services and the infrastructure needed to ship and operate them.',
stack: ['C# / .NET', 'Vulkan', 'Go', 'Docker', 'Jenkins', 'Linux'],
},
],
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,
number: '01',
name: 'Cortex Engine',
kind: 'AI-native 3D engine',
description: 'A 3D engine with a pure P/Invoke Vulkan 1.3 renderer, PBR shading, cubemap-array shadows, Jolt physics and Flecs ECS. An MCP interface exposes scene operations to AI agents, including object creation, transforms and screenshots.',
highlights: ['Pure Vulkan 1.3 backend', 'PBR + multi-light shadows', 'Playable FPS demo', '7 MCP scene tools'],
tech: ['C#', '.NET 9', 'Vulkan', 'SDL3', 'Jolt', 'Flecs', 'MCP'],
href: 'https://github.com/emil28092005/Cortex_Engine',
},
{
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,
number: '02',
name: 'SciMesh',
kind: 'Scientific computing framework',
description: 'A local Python framework for exact molecular similarity search and sparse similarity-graph construction over ChEMBL-style datasets. It streams input data, bounds top-k memory use and avoids dense N×N similarity matrices.',
highlights: ['Morgan fingerprints', 'Exact Tanimoto search', 'Block-wise graph construction', 'Tested CLI package'],
tech: ['Python', 'RDKit', 'ChEMBL', 'Pytest'],
href: 'https://github.com/emil28092005/SciMesh',
},
{
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,
number: '03',
name: 'Bioportal Lab',
kind: 'Laboratory CV prototype',
description: 'A Russian-language prototype for microbiology workflows. PetriCount combines multi-scale computer-vision candidates, a Random Forest classifier and manual correction to support colony counting from Petri-dish images.',
highlights: ['Image upload workflow', 'Trainable CV baseline', 'Manual result correction', 'Local analysis API'],
tech: ['Python', 'Flask', 'OpenCV', 'scikit-learn', 'JavaScript'],
href: 'https://github.com/emil28092005/Bioportal-lab',
},
{
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,
number: '04',
name: 'Shagram',
kind: 'Backend & DevOps system',
description: 'A real-time WebSocket chat used as an end-to-end infrastructure project: JWT authentication, SQLite persistence, containerized delivery, TLS termination and a Jenkins pipeline connected to a private Harbor registry.',
highlights: ['WebSocket rooms', 'JWT + origin validation', 'Multi-stage containers', 'Automated CI/CD on VPS'],
tech: ['Go', 'Gin', 'WebSocket', 'Docker', 'Nginx', 'Jenkins', 'Harbor'],
href: 'https://github.com/emil28092005/shagram',
},
{
number: '05',
name: 'Memba + memwalk',
kind: 'Experimental model memory',
description: 'An experimental stack for saving and restoring persistent state in SSM-based language models. memwalk builds on memba to digest codebases once, cache model state and answer later questions without repeatedly ingesting the same repository.',
highlights: ['Portable state snapshots', 'C and Python APIs', 'Manifest-based cache invalidation', 'MCP server for codebase queries'],
tech: ['C++', 'Python', 'CMake', 'llama.cpp', 'Mamba / SSM', 'MCP'],
href: 'https://github.com/emil28092005/Memba',
secondaryHref: 'https://github.com/emil28092005/memwalk',
},
],
tools: [
{ name: 'Soundgen', note: 'Rust-based 8-bit sound synthesis and feedback tooling', href: 'https://github.com/emil28092005/Soundgen' },
{ name: 'KimiFleet', note: 'Visible multi-agent orchestrator over ACP and MCP', href: 'https://github.com/emil28092005/KimiFleet' },
{ name: 'Imagen', note: 'Local text-to-sprite MCP pipeline with rating workflow', href: 'https://github.com/emil28092005/Imagen' },
{ name: 'MoME', note: 'Sparse-gated personal memory plugin for Hermes Agent', href: 'https://github.com/emil28092005/hermes-plugin-mome' },
],
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]
-112
View File
@@ -1,112 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ if .IsHome }}{{ .Site.Params.author }} — {{ .Site.Params.description }}{{ else }}{{ .Title }} | {{ .Site.Params.author }}{{ end }}</title>
{{ $css := resources.Get "css/main.css" }}
{{ if $css }}<link rel="stylesheet" href="{{ $css.RelPermalink }}">{{ end }}
</head>
<body class="bg-slate-950 text-slate-100 min-h-screen">
<div class="h-px bg-gradient-to-r from-blue-600 via-cyan-400 to-blue-600"></div>
<nav class="sticky top-0 z-10 bg-slate-950/80 backdrop-blur border-b border-slate-800/60 px-8 py-3 flex items-center justify-between">
<span class="text-sm font-mono text-slate-500 tracking-wide">{{ .Site.Params.author }}</span>
<button id="pdf-btn" onclick="downloadPDF()"
class="text-xs text-slate-500 hover:text-white transition-colors px-3 py-1.5 rounded border border-slate-700 hover:border-slate-500 cursor-pointer">
Download PDF
</button>
</nav>
<main id="resume-main" class="max-w-3xl mx-auto px-6 py-14">
{{ block "main" . }}{{ end }}
</main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js" defer></script>
<script>
function downloadPDF() {
const btn = document.getElementById('pdf-btn');
btn.textContent = 'Generating…';
btn.disabled = true;
html2pdf().set({
margin: [14, 18, 14, 18],
filename: 'Emil_Shanayev_Resume.pdf',
image: { type: 'png' },
html2canvas: {
scale: 2.5,
useCORS: true,
onclone: (doc) => {
// Inject a stylesheet — inline styles lose to !important in our CSS,
// but a later <style> tag wins the cascade.
const style = doc.createElement('style');
style.textContent = `
body {
background: #ffffff !important;
background-image: none !important;
color: #0f172a !important;
min-height: auto !important;
}
#resume-main {
max-width: none !important;
margin: 0 !important;
padding: 0 !important;
background: #ffffff !important;
}
h1 {
background-image: none !important;
-webkit-background-clip: unset !important;
background-clip: unset !important;
-webkit-text-fill-color: #0f172a !important;
color: #0f172a !important;
}
header { border-color: #e2e8f0 !important; }
header p { color: #475569 !important; }
header span {
background: #eff6ff !important;
border-color: #bfdbfe !important;
color: #1d4ed8 !important;
}
header a {
background: transparent !important;
border: none !important;
color: #1d4ed8 !important;
padding: 0 !important;
font-size: 0.8rem !important;
}
.resume-content h2 {
color: #1d4ed8 !important;
background: #eff6ff !important;
border-color: #1d4ed8 !important;
box-shadow: none !important;
margin-left: 0 !important;
border-radius: 0.25rem !important;
}
.resume-content h3 { color: #1e293b !important; }
.resume-content p { color: #334155 !important; }
.resume-content li { color: #334155 !important; }
.resume-content li::marker { color: #1d4ed8 !important; }
.resume-content strong { color: #0f172a !important; }
.resume-content a { color: #1d4ed8 !important; }
.resume-content code { background: #f1f5f9 !important; color: #1d4ed8 !important; }
.resume-content hr { border-color: #e2e8f0 !important; }
.skills-grid span {
background: #eff6ff !important;
border-color: #bfdbfe !important;
color: #1d4ed8 !important;
}
`;
doc.head.appendChild(style);
}
},
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
pagebreak: { mode: 'css' }
})
.from(document.getElementById('resume-main'))
.save()
.then(() => {
btn.textContent = 'Download PDF';
btn.disabled = false;
});
}
</script>
</body>
</html>
-6
View File
@@ -1,6 +0,0 @@
{{ define "main" }}
<article class="prose prose-invert max-w-none">
<h1 class="text-3xl font-bold mb-6">{{ .Title }}</h1>
{{ .Content }}
</article>
{{ end }}
-31
View File
@@ -1,31 +0,0 @@
{{ define "main" }}
<header class="mb-14 pb-10 border-b border-slate-800">
<div class="flex flex-wrap gap-2 mb-5">
<span class="px-2.5 py-1 rounded-full text-xs font-mono bg-blue-500/10 text-blue-400 border border-blue-500/20">DevOps</span>
<span class="px-2.5 py-1 rounded-full text-xs font-mono bg-blue-500/10 text-blue-400 border border-blue-500/20">Backend</span>
<span class="px-2.5 py-1 rounded-full text-xs font-mono bg-blue-500/10 text-blue-400 border border-blue-500/20">Go · Python</span>
<span class="px-2.5 py-1 rounded-full text-xs font-mono bg-blue-500/10 text-blue-400 border border-blue-500/20">Kubernetes</span>
</div>
<h1 class="text-5xl font-bold tracking-tight bg-gradient-to-br from-white via-slate-200 to-slate-500 bg-clip-text text-transparent">
{{ .Site.Params.author }}
</h1>
<p class="mt-3 text-slate-400 text-lg">{{ .Site.Params.description }}</p>
<div class="mt-6 flex flex-wrap gap-3">
{{ if .Site.Params.github }}
<a href="{{ .Site.Params.github }}"
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 text-sm text-slate-300 hover:text-white transition-all border border-slate-700">
GitHub ↗
</a>
{{ end }}
{{ if .Site.Params.email }}
<a href="mailto:{{ .Site.Params.email }}"
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-800/50 hover:bg-slate-700 text-sm text-slate-400 hover:text-white transition-all border border-slate-700/60">
{{ .Site.Params.email }}
</a>
{{ end }}
</div>
</header>
<article class="resume-content prose prose-invert max-w-none">
{{ .Content }}
</article>
{{ end }}
-5
View File
@@ -1,5 +0,0 @@
<div class="skills-grid">
{{ range .Params }}
<span>{{ . }}</span>
{{ end }}
</div>
+2 -1
View File
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+3 -7
View File
@@ -1,13 +1,9 @@
/** @type {import('next').NextConfig} */
const REPO_NAME = 'Emil_Shanayev_Resume'
const nextConfig = {
output: 'export',
basePath: `/${REPO_NAME}`,
assetPrefix: `/${REPO_NAME}`,
images: {
unoptimized: true,
},
basePath: '/Emil_Shanayev_Resume',
assetPrefix: '/Emil_Shanayev_Resume',
images: { unoptimized: true },
}
export default nextConfig
+666 -1363
View File
File diff suppressed because it is too large Load Diff
+10 -15
View File
@@ -1,26 +1,21 @@
{
"name": "emil-shanayev-portfolio",
"version": "1.0.0",
"name": "emil-shanayev-resume",
"version": "2.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"start": "next start"
},
"dependencies": {
"framer-motion": "^11.0.8",
"next": "^14.2.5",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"next": "15.5.21",
"react": "19.2.4",
"react-dom": "19.2.4"
},
"devDependencies": {
"@types/node": "^20.14.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5"
"@types/node": "22.10.2",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"typescript": "5.7.2"
}
}
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
-44
View File
@@ -1,44 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
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: ['JetBrains Mono', 'ui-monospace', 'monospace'],
},
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: [],
}
-59
View File
@@ -1,59 +0,0 @@
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
-1
View File
@@ -1 +0,0 @@
# Identity Template
-1
View File
@@ -1 +0,0 @@
# Preferences Template
+3 -5
View File
@@ -1,7 +1,8 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"allowJs": false,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
@@ -13,10 +14,7 @@
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./*"]
},
"target": "ES2017"
"paths": { "@/*": ["./*"] }
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
File diff suppressed because one or more lines are too long