From 32c57a5adaa083f9482bd6b23c58265e0c7a0a9b Mon Sep 17 00:00:00 2001 From: Emil Date: Sun, 20 Sep 2026 20:55:05 +0300 Subject: [PATCH] Resume site with live content from Gitea Replaces the static portfolio with a resume site that reads /data/resume.json: the text comes from data/resume.json in this repo, and the projects are the public Gitea repos tagged `resume`. Merges in the content of the hh.ru resume (backend/DevOps skills, experience, languages) without personal details. Co-Authored-By: Claude Sonnet 5 --- README.md | 37 ++--- app/globals.css | 40 +++++ app/layout.tsx | 4 +- app/page.tsx | 130 +--------------- app/site.tsx | 212 +++++++++++++++++++++++++++ data/resume.json | 366 ++++++++++++++++++++++++++++++++++++++++++++++ data/resume.ts | 102 ------------- deploy-shanaty.sh | 32 ++++ lib/types.ts | 39 +++++ next.config.mjs | 6 +- 10 files changed, 719 insertions(+), 249 deletions(-) create mode 100644 app/site.tsx create mode 100644 data/resume.json delete mode 100644 data/resume.ts create mode 100755 deploy-shanaty.sh create mode 100644 lib/types.ts diff --git a/README.md b/README.md index ffaec0a..a5b47ca 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,31 @@ -# Emil Shanayev — Portfolio +# Emil Shanayev — resume site -Updated static portfolio based on the original `Emil_Shanayev_Resume` repository. +Live at **https://shanaty.ru/resume/**. A static Next.js site whose content is loaded live from Gitea: -## Local development +- **Text** (name, bio, skills, experience, education, contacts, …) comes from [`data/resume.json`](data/resume.json) in this repo. +- **Projects** are the public repos on Gitea (`git.shanaty.ru/Emil_Shanaty`) that carry the topic **`resume`**. Tag a repo and it appears within about a minute; remove the topic and it disappears. +- Optional `projectNotes` in `data/resume.json` (keyed by repo name) enrich a project with a title, kind, longer description, highlights, tech tags, extra links and a `rank` (lower ranks first; the rest are newest first). Without a note a project shows its Gitea description, language and topics. + +## How it works + +A systemd timer on the server (`resume_sync.py`, in the `auto` repo) rebuilds `https://shanaty.ru/data/resume.json` every minute from the `main` branch of this repo plus the topic search. The page fetches that file on load and falls back to the copy bundled at build time if it is unreachable. A broken `data/resume.json` (invalid JSON, missing fields, non-http links) is rejected and the previous published data stays. + +To update content: edit `data/resume.json`, commit, and **push to Gitea** (`git push gitea main`). No rebuild or deploy is needed. + +## Development ```bash npm install -npm run dev +npm run dev # http://localhost:3000, data from the public https://shanaty.ru/data/resume.json +npm run build # static export in out/ ``` -Open `http://localhost:3000`. +`BASE_PATH` sets the URL prefix (default `/Emil_Shanayev_Resume`, for GitHub Pages) and `NEXT_PUBLIC_DATA_URL` the data URL. -## Production build +## Deploy -```bash -npm run build -``` +`./deploy-shanaty.sh` builds and publishes the site to shanaty.ru (needs the ssh key `~/.ssh/MyGitea`). Only needed after changing code or styles. -The static export is written to `out/`. +## Not published on purpose -## 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. +The official resume on hh.ru holds more personal details (phone, date of birth, salary expectations). This site only carries the professional content. diff --git a/app/globals.css b/app/globals.css index 519b4a7..a51f572 100644 --- a/app/globals.css +++ b/app/globals.css @@ -102,3 +102,43 @@ a:focus-visible { outline:2px solid var(--acid); outline-offset:5px; } .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; } } + +/* Resume site: skills, experience, live project list, side info. */ +.skill-grid { display:grid; grid-template-columns:repeat(3,1fr); } +.skill-card { padding:34px 28px 34px 0; border-right:1px solid var(--line); border-bottom:1px solid var(--line); transition:background .25s; } +.skill-card:nth-child(3n+2), .skill-card:nth-child(3n) { padding-left:28px; } +.skill-card:nth-child(3n) { border-right:0; } +.skill-card:hover { background:linear-gradient(135deg,rgba(199,255,70,.09),transparent 68%); } +.skill-card h3 { font-size:22px; margin:30px 0 12px; letter-spacing:-.035em; } +.skill-card p { margin:0; color:#b0bab2; font-size:14px; line-height:1.65; } +.exp-item { display:grid; grid-template-columns:200px 1fr; gap:34px; padding:34px 0; border-bottom:1px solid var(--line); } +.exp-item > span { font:300 11px/1.6 'DM Mono',monospace; color:var(--muted); text-transform:uppercase; letter-spacing:.05em; } +.exp-item h3 { margin:0 0 16px; font-size:22px; letter-spacing:-.03em; } +.exp-item ul { margin:0; padding:0; list-style:none; display:grid; gap:9px; color:#b0bab2; line-height:1.6; } +.exp-item li::before { content:'—'; color:var(--muted); margin-right:10px; } +.status-note { margin:0; padding:44px 0; border-bottom:1px solid var(--line); color:var(--muted); font:400 12px/1.8 'DM Mono',monospace; } +.status-note a { color:var(--acid); border-bottom:1px solid rgba(199,255,70,.4); } +.status-note b { color:var(--ink); font-weight:500; } +.project-updated { margin:20px 0 0; color:var(--muted); font:300 10px 'DM Mono',monospace; text-transform:uppercase; letter-spacing:.05em; } +.about-grid { display:grid; grid-template-columns:1.25fr 1fr; gap:64px; padding-bottom:70px; } +.about-grid .education-list { padding-bottom:0; } +.side-info { padding-top:18px; } +.mini-heading { margin:0 0 14px; padding-bottom:10px; border-bottom:1px solid rgba(243,246,240,.35); font:500 11px 'DM Mono',monospace; text-transform:uppercase; letter-spacing:.11em; } +.side-info .mini-heading:not(:first-child) { margin-top:40px; } +.lang-list { list-style:none; margin:0; padding:0; } +.lang-list li { display:flex; justify-content:space-between; padding:10px 0; border-bottom:1px solid var(--line); } +.lang-list b { font:400 11px 'DM Mono',monospace; color:var(--acid); font-weight:400; } +.side-info .tag-list { margin-top:0; } +.contact-note { color:#92958a; font-style:normal; font-size:12px; } +@media (max-width:980px) { + .skill-grid { grid-template-columns:repeat(2,1fr); } + .skill-card:nth-child(n) { padding:34px 24px 34px 0; border-right:1px solid var(--line); } + .skill-card:nth-child(2n) { padding-left:24px; border-right:0; } + .about-grid { grid-template-columns:1fr; gap:40px; } +} +@media (max-width:760px) { + .skill-grid { grid-template-columns:1fr; } + .skill-card:nth-child(n) { padding:30px 0; border-right:0; } + .exp-item { grid-template-columns:1fr; gap:14px; padding:28px 0; } + .about-grid { padding-bottom:50px; } +} diff --git a/app/layout.tsx b/app/layout.tsx index 8e7344d..c830ca8 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,8 +2,8 @@ import type { Metadata } from 'next' import './globals.css' export const metadata: Metadata = { - title: 'Emil Shanayev — AI Systems & Scientific Software', - description: 'Portfolio of Emil Shanayev: AI-native systems, scientific software, graphics and infrastructure.', + title: 'Emil Shanayev — Resume', + description: 'Resume of Emil Shanayev: backend, DevOps, AI systems, scientific software and game technology.', authors: [{ name: 'Emil Shanayev' }], } diff --git a/app/page.tsx b/app/page.tsx index 107afa8..ab37908 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,129 +1,7 @@ -import { resume } from '@/data/resume' - -const ArrowIcon = () => ( - -) +import resume from '@/data/resume.json' +import type { Resume } from '@/lib/types' +import Site from './site' export default function Home() { - return ( -
-
- ) + return } diff --git a/app/site.tsx b/app/site.tsx new file mode 100644 index 0000000..506e26a --- /dev/null +++ b/app/site.tsx @@ -0,0 +1,212 @@ +'use client' + +import { useEffect, useState } from 'react' +import type { Live, Project, Resume } from '@/lib/types' + +// Deployed on shanaty.ru the build points at the same-origin file; elsewhere (dev, GitHub Pages) it uses the public URL. +const DATA_URL = process.env.NEXT_PUBLIC_DATA_URL ?? 'https://shanaty.ru/data/resume.json' +const GITEA_PROFILE = 'https://git.shanaty.ru/Emil_Shanaty' + +const pad = (n: number) => String(n).padStart(2, '0') +const monthYear = (iso?: string) => (iso ? new Date(iso).toLocaleDateString('en', { month: 'short', year: 'numeric' }) : '') + +const ArrowIcon = () => ( + +) + +type Status = 'loading' | 'live' | 'offline' + +export default function Site({ initial }: Readonly<{ initial: Resume }>) { + const [resume, setResume] = useState(initial) + const [projects, setProjects] = useState([]) + const [generatedAt, setGeneratedAt] = useState() + const [status, setStatus] = useState('loading') + + useEffect(() => { + const controller = new AbortController() + fetch(DATA_URL, { cache: 'no-store', signal: controller.signal }) + .then((response) => (response.ok ? response.json() : Promise.reject(new Error(`HTTP ${response.status}`)))) + .then((data: Live) => { + if (!data?.resume || !Array.isArray(data.projects)) throw new Error('unexpected data') + setResume(data.resume) + setProjects(data.projects) + setGeneratedAt(data.generated_at) + setStatus('live') + }) + .catch((error: Error) => { + if (error.name !== 'AbortError') setStatus('offline') + }) + return () => controller.abort() + }, []) + + // Once the list has loaded and is empty (no repo carries the `resume` topic yet), the section is hidden entirely. + const showProjects = status !== 'live' || projects.length > 0 + const primary = resume.contacts[0] + const github = resume.contacts.find((contact) => contact.label === 'GitHub') + + return ( +
+
+ ) +} diff --git a/data/resume.json b/data/resume.json new file mode 100644 index 0000000..370389b --- /dev/null +++ b/data/resume.json @@ -0,0 +1,366 @@ +{ + "name": "Emil Shanayev", + "eyebrow": "backend · devops · AI systems · scientific computing · game tech", + "role": "Backend, DevOps & AI Systems Developer", + "bio": "I build AI-native products, backend services and the infrastructure to run them: from Python/FastAPI and Go services, WebSocket APIs and containerised CI/CD pipelines to distributed molecular workloads, LLM-controlled worlds and a Vulkan game engine. I run self-hosted services on my own Linux servers.", + "location": "Russia", + "availability": "Open to internships, full-time, part-time and project work · remote, hybrid or on-site", + "contacts": [ + { + "label": "Email", + "value": "emil28092005@yandex.ru", + "href": "mailto:emil28092005@yandex.ru", + "note": "preferred" + }, + { + "label": "Email", + "value": "emil28092005@gmail.com", + "href": "mailto:emil28092005@gmail.com" + }, + { + "label": "Telegram", + "value": "@emilshanaty", + "href": "https://t.me/emilshanaty" + }, + { + "label": "GitHub", + "value": "emil28092005", + "href": "https://github.com/emil28092005" + }, + { + "label": "Gitea", + "value": "git.shanaty.ru/Emil_Shanaty", + "href": "https://git.shanaty.ru/Emil_Shanaty" + } + ], + "skills": [ + { + "title": "Backend & web", + "text": "Asynchronous REST and WebSocket services, database design and query optimisation.", + "stack": [ + "Python", + "FastAPI", + "asyncio", + "Go", + "Rust", + "TypeScript", + "SQL", + "PostgreSQL", + "SQLite", + "Redis", + "WebSocket", + "JSON-RPC", + "Java (basic)" + ] + }, + { + "title": "DevOps & infrastructure", + "text": "Containerised delivery, CI/CD automation and day-to-day Linux server administration.", + "stack": [ + "Docker", + "docker-compose", + "Kubernetes", + "GitHub Actions", + "Jenkins", + "Harbor", + "Linux", + "Bash", + "VPS", + "Debian", + "Ubuntu" + ] + }, + { + "title": "AI-native systems", + "text": "Agent protocols, recursive research teams, MCP integrations and human-in-the-loop feedback systems.", + "stack": [ + "MCP", + "ACP / JSON-RPC", + "LLM APIs", + "TypeScript", + "Python" + ] + }, + { + "title": "Scientific software", + "text": "Molecular data processing, cheminformatics workloads, deterministic pipelines and reproducible command-line tools.", + "stack": [ + "Python", + "RDKit", + "ChEMBL", + "Go", + "PostgreSQL" + ] + }, + { + "title": "Graphics & game tech", + "text": "Performance-oriented engines, native audio tools and 3D content pipelines.", + "stack": [ + "C# / .NET", + "Vulkan", + "Unity", + "Blender", + "Rust", + "C++" + ] + }, + { + "title": "Team & process", + "text": "Leading small teams, Agile/Scrum coordination and clear communication with stakeholders.", + "stack": [ + "Agile", + "Scrum", + "Team leading", + "Git", + "GitHub" + ] + } + ], + "experience": [ + { + "title": "Independent projects & self-directed engineering", + "meta": "Ongoing", + "points": [ + "Full-featured asynchronous backend applications in Python/FastAPI.", + "Containerised applications with Docker, deployed to Kubernetes.", + "CI/CD pipelines with GitHub Actions for automated testing and deployment.", + "Administration of Linux servers (Debian VPS) and self-hosted services.", + "Databases in practice: SQLite, Redis, PostgreSQL." + ] + }, + { + "title": "Team experience", + "meta": "University and agency-style projects", + "points": [ + "Led teams in university projects and managed a team in an agency-style setting.", + "Worked in Agile environments using Scrum; task distribution and coordination.", + "Regular communication with team members and stakeholders." + ] + } + ], + "education": [ + { + "period": "2026 — 2030", + "institution": "HSE University, Moscow", + "program": "Bachelor’s programme · Game and Digital Product Development · 1st-year student" + }, + { + "period": "2023 — 2025", + "institution": "Innopolis University", + "program": "Data Structures and Artificial Intelligence (DSAI) · 2 years, incomplete higher education" + } + ], + "languages": [ + { + "name": "Russian", + "level": "Native" + }, + { + "name": "English", + "level": "B2" + } + ], + "interests": [ + "DevOps & Infrastructure as Code", + "High-load backend systems", + "Linux & systems administration", + "Computer graphics & game development", + "Open source", + "Learning from documentation and by building" + ], + "projectNotes": { + "Cortex_Engine": { + "title": "Cortex Engine", + "rank": 1, + "kind": "AI-native 3D game engine", + "description": "A C#/.NET 9 game engine with a pure P/Invoke Vulkan 1.3 renderer, PBR shading, cubemap-array shadows, native Box3D physics and Flecs ECS. Its editor and runtime expose scene operations to AI agents through MCP.", + "highlights": [ + "Pure Vulkan 1.3 backend", + "PBR + multi-light shadows", + "Playable FPS demo", + "7 MCP scene tools" + ], + "tech": [ + "C#", + ".NET 9", + "Vulkan", + "SDL3", + "Box3D", + "Flecs", + "MCP" + ], + "links": [ + { + "label": "GitHub", + "href": "https://github.com/emil28092005/Cortex_Engine" + } + ] + }, + "SciMesh": { + "title": "SciMesh", + "rank": 2, + "kind": "Distributed scientific computing platform", + "description": "A local-first platform that turns molecular computations into independent tasks, dispatches them to Go workers and deterministically combines checksum-protected artifacts. The Python SDK provides exact ChEMBL workloads without dense N×N matrices.", + "highlights": [ + "Python workload SDK", + "Go coordinator + workers", + "PostgreSQL / SQLite modes", + "Cross-platform signed releases" + ], + "tech": [ + "Python", + "Go", + "RDKit", + "PostgreSQL", + "Docker", + "SHA-256" + ], + "links": [ + { + "label": "GitHub", + "href": "https://github.com/emil28092005/SciMesh" + } + ] + }, + "AICC-Protocol": { + "title": "AICC Protocol", + "rank": 3, + "kind": "LLM-controlled character protocol", + "description": "An engine-agnostic protocol and Python SDK for connecting language-model agents to virtual environments. The reference capsule testbed proves the full loop: perception, movement and interaction over WebSocket with a protocol-conformant bridge.", + "highlights": [ + "Async-first Python SDK", + "WebSocket + in-process transports", + "9/9 conformance scenarios", + "Vision and navigation demo" + ], + "tech": [ + "Python", + "JSON Schema", + "WebSocket", + "Pydantic", + "LLM agents" + ], + "links": [ + { + "label": "GitHub", + "href": "https://github.com/emil28092005/AICC-Protocol" + }, + { + "label": "aicc-py", + "href": "https://github.com/emil28092005/aicc-py" + } + ] + }, + "hypothesis-machine": { + "title": "Hypothesis Machine", + "rank": 4, + "kind": "Recursive research-agent extension", + "description": "A TypeScript extension for the Pi Coding Agent that turns one interactive session into a persistent, recursive research team. It combines parallel agents, recoverable trees, full-text research memory and isolated Docker experiments with independent review.", + "highlights": [ + "Recursive / parallel agents", + "Persistent tree recovery", + "SQLite FTS research memory", + "Sandboxed experiment runner" + ], + "tech": [ + "TypeScript", + "Node.js 22", + "SQLite", + "Docker", + "Pi Agent" + ], + "links": [ + { + "label": "GitHub", + "href": "https://github.com/emil28092005/hypothesis-machine" + } + ] + }, + "bibi_bot": { + "title": "Bibi Bot", + "rank": 5, + "kind": "Self-hosted Telegram AI assistant", + "description": "A production-oriented Telegram assistant for private groups: one-way HLS game streaming in a Mini App, DeepSeek-backed chat, repository-grounded answers, scoped SQLite memory and PostgreSQL-backed team context.", + "highlights": [ + "Telegram Mini App + HLS", + "Repository research with partial clones", + "Scoped memory and privacy controls", + "Docker Compose deployment" + ], + "tech": [ + "Python", + "FastAPI", + "Telegram", + "DeepSeek", + "PostgreSQL", + "Docker" + ], + "links": [ + { + "label": "GitHub", + "href": "https://github.com/emil28092005/bibi_bot" + } + ] + }, + "Soundgen": { + "title": "Soundgen", + "rank": 11, + "kind": "Tool / experiment", + "description": "Rust 8-bit synthesis, MCP generation and human-feedback training loop", + "links": [ + { + "label": "GitHub", + "href": "https://github.com/emil28092005/Soundgen" + } + ] + }, + "KimiFleet": { + "title": "KimiFleet", + "rank": 12, + "kind": "Tool / experiment", + "description": "Visible multi-agent orchestrator over ACP with live dashboard and handoffs", + "links": [ + { + "label": "GitHub", + "href": "https://github.com/emil28092005/KimiFleet" + } + ] + }, + "Imagen": { + "title": "Imagen", + "rank": 13, + "kind": "Tool / experiment", + "description": "MCP pixel-art generator with transparent backgrounds and rated references", + "links": [ + { + "label": "GitHub", + "href": "https://github.com/emil28092005/Imagen" + } + ] + }, + "Fractalier": { + "title": "Fractalier", + "rank": 14, + "kind": "Tool / experiment", + "description": "Procedural fractal evolution game with selection, crossover and mutation", + "links": [ + { + "label": "GitHub", + "href": "https://github.com/emil28092005/Fractalier" + } + ] + }, + "hermes-plugin-mome": { + "title": "MoME", + "rank": 15, + "kind": "Tool / experiment", + "description": "Sparse-gated personal memory plugin with an online-learning router", + "links": [ + { + "label": "GitHub", + "href": "https://github.com/emil28092005/hermes-plugin-mome" + } + ] + } + } +} diff --git a/data/resume.ts b/data/resume.ts deleted file mode 100644 index 53512d4..0000000 --- a/data/resume.ts +++ /dev/null @@ -1,102 +0,0 @@ -export const resume = { - name: 'Emil Shanayev', - eyebrow: 'AI systems · scientific computing · game technology', - role: 'AI Systems & Scientific Software Developer', - bio: 'I build AI-native products and developer infrastructure across scientific computing, agent protocols and game technology — from distributed molecular workloads and LLM-controlled worlds to a Vulkan engine and production Telegram services.', - 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: 'Email', value: 'emil28092005@yandex.ru', href: 'mailto:emil28092005@yandex.ru' }, - { label: 'Telegram', value: '@emilshanaty', href: 'https://t.me/emilshanaty' }, - ], - capabilities: [ - { - index: '01', - title: 'Scientific software', - text: 'Molecular data processing, cheminformatics workloads, deterministic pipelines and reproducible command-line tools.', - stack: ['Python', 'RDKit', 'ChEMBL', 'Go', 'PostgreSQL'], - }, - { - index: '02', - title: 'AI-native systems', - text: 'Agent protocols, recursive research teams, MCP integrations and human-in-the-loop feedback systems.', - stack: ['MCP', 'ACP / JSON-RPC', 'LLM APIs', 'TypeScript', 'Python'], - }, - { - index: '03', - title: 'Systems & infrastructure', - text: 'Performance-oriented engines, native audio tools, backend services and the infrastructure needed to ship them.', - stack: ['C# / .NET', 'Vulkan', 'Rust', 'Docker', 'WebSocket', 'Linux'], - }, - ], - projects: [ - { - number: '01', - name: 'Cortex Engine', - kind: 'AI-native 3D game engine', - description: 'A C#/.NET 9 game engine with a pure P/Invoke Vulkan 1.3 renderer, PBR shading, cubemap-array shadows, native Box3D physics and Flecs ECS. Its editor and runtime expose scene operations to AI agents through MCP.', - highlights: ['Pure Vulkan 1.3 backend', 'PBR + multi-light shadows', 'Playable FPS demo', '7 MCP scene tools'], - tech: ['C#', '.NET 9', 'Vulkan', 'SDL3', 'Box3D', 'Flecs', 'MCP'], - href: 'https://github.com/emil28092005/Cortex_Engine', - }, - { - number: '02', - name: 'SciMesh', - kind: 'Distributed scientific computing platform', - description: 'A local-first platform that turns molecular computations into independent tasks, dispatches them to Go workers and deterministically combines checksum-protected artifacts. The Python SDK provides exact ChEMBL workloads without dense N×N matrices.', - highlights: ['Python workload SDK', 'Go coordinator + workers', 'PostgreSQL / SQLite modes', 'Cross-platform signed releases'], - tech: ['Python', 'Go', 'RDKit', 'PostgreSQL', 'Docker', 'SHA-256'], - href: 'https://github.com/emil28092005/SciMesh', - }, - { - number: '03', - name: 'AICC Protocol', - kind: 'LLM-controlled character protocol', - description: 'An engine-agnostic protocol and Python SDK for connecting language-model agents to virtual environments. The reference capsule testbed proves the full loop: perception, movement and interaction over WebSocket with a protocol-conformant bridge.', - highlights: ['Async-first Python SDK', 'WebSocket + in-process transports', '9/9 conformance scenarios', 'Vision and navigation demo'], - tech: ['Python', 'JSON Schema', 'WebSocket', 'Pydantic', 'LLM agents'], - href: 'https://github.com/emil28092005/AICC-Protocol', - secondaryHref: 'https://github.com/emil28092005/aicc-py', - secondaryLabel: 'aicc-py', - }, - { - number: '04', - name: 'Hypothesis Machine', - kind: 'Recursive research-agent extension', - description: 'A TypeScript extension for the Pi Coding Agent that turns one interactive session into a persistent, recursive research team. It combines parallel agents, recoverable trees, full-text research memory and isolated Docker experiments with independent review.', - highlights: ['Recursive / parallel agents', 'Persistent tree recovery', 'SQLite FTS research memory', 'Sandboxed experiment runner'], - tech: ['TypeScript', 'Node.js 22', 'SQLite', 'Docker', 'Pi Agent'], - href: 'https://github.com/emil28092005/hypothesis-machine', - }, - { - number: '05', - name: 'Bibi Bot', - kind: 'Self-hosted Telegram AI assistant', - description: 'A production-oriented Telegram assistant for private groups: one-way HLS game streaming in a Mini App, DeepSeek-backed chat, repository-grounded answers, scoped SQLite memory and PostgreSQL-backed team context.', - highlights: ['Telegram Mini App + HLS', 'Repository research with partial clones', 'Scoped memory and privacy controls', 'Docker Compose deployment'], - tech: ['Python', 'FastAPI', 'Telegram', 'DeepSeek', 'PostgreSQL', 'Docker'], - href: 'https://github.com/emil28092005/bibi_bot', - }, - ], - tools: [ - { name: 'Soundgen', note: 'Rust 8-bit synthesis, MCP generation and human-feedback training loop', href: 'https://github.com/emil28092005/Soundgen' }, - { name: 'KimiFleet', note: 'Visible multi-agent orchestrator over ACP with live dashboard and handoffs', href: 'https://github.com/emil28092005/KimiFleet' }, - { name: 'Imagen', note: 'MCP pixel-art generator with transparent backgrounds and rated references', href: 'https://github.com/emil28092005/Imagen' }, - { name: 'Fractalier', note: 'Procedural fractal evolution game with selection, crossover and mutation', href: 'https://github.com/emil28092005/Fractalier' }, - { name: 'MoME', note: 'Sparse-gated personal memory plugin with an online-learning router', href: 'https://github.com/emil28092005/hermes-plugin-mome' }, - ], - education: [ - { - period: '2023—2025', - institution: 'Innopolis University', - program: 'Data Structures and Artificial Intelligence (DSAI)', - }, - { - period: '2026—present', - institution: 'HSE University', - program: '1st-year student · Bachelor’s programme', - }, - ], -} as const diff --git a/deploy-shanaty.sh b/deploy-shanaty.sh new file mode 100755 index 0000000..9f86983 --- /dev/null +++ b/deploy-shanaty.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Build the site and publish it at https://shanaty.ru/resume/ (Gitea/Caddy server). +# +# The text and project list are NOT baked into the build: the page reads /data/resume.json, which the +# server rebuilds every minute from data/resume.json in the Gitea repo plus the repos tagged `resume`. +# So redeploy only after changing code or styles; content changes just need a push to Gitea. +# The site is uploaded to a temp dir and swapped in, so it is never half-updated. +set -euo pipefail +cd "$(dirname "$0")" + +HOST="${GT2GH_HOST:-root@135.106.198.222}" +KEY="${GT2GH_KEY:-$HOME/.ssh/MyGitea}" +SITE_DIR=/opt/gitea/site + +[ -r "$KEY" ] || { echo "deploy: ssh key not found: $KEY" >&2; exit 1; } + +rm -rf .next out +BASE_PATH=/resume NEXT_PUBLIC_DATA_URL=/data/resume.json npm run build +[ -f out/index.html ] || { echo "deploy: build failed (out/index.html missing)" >&2; exit 1; } + +tar -C out -c . | ssh -i "$KEY" -o IgnoreUnknown=WarnWeakCrypto -o WarnWeakCrypto=no "$HOST" " + set -e + cd $SITE_DIR + rm -rf .resume.new .resume.old + mkdir .resume.new + tar -x -C .resume.new --no-same-owner + chmod -R a+rX .resume.new + if [ -d resume ]; then mv resume .resume.old; fi + mv .resume.new resume + rm -rf .resume.old +" +echo "Deployed: https://shanaty.ru/resume/" diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 0000000..39030da --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,39 @@ +export type Contact = { label: string; value: string; href: string; note?: string } +export type Skill = { title: string; text: string; stack: string[] } +export type Experience = { title: string; meta?: string; points: string[] } +export type Education = { period: string; institution: string; program: string } +export type Link = { label: string; href: string } + +export type Resume = { + name: string + eyebrow: string + role: string + bio: string + location: string + availability: string + contacts: Contact[] + skills: Skill[] + experience: Experience[] + education: Education[] + languages: { name: string; level: string }[] + interests: string[] +} + +// A public Gitea repository tagged `resume`, merged with the optional notes from data/resume.json. +export type Project = { + name: string + title?: string + url: string + description?: string + kind?: string + highlights?: string[] + tech: string[] + links?: Link[] + homepage?: string + language?: string + stars?: number + updated?: string +} + +// Shape of /data/resume.json, written every minute by the server-side sync. +export type Live = { generated_at: string; resume: Resume; projects: Project[] } diff --git a/next.config.mjs b/next.config.mjs index d1d952a..1478806 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,8 +1,10 @@ +// BASE_PATH overrides the GitHub Pages default, e.g. BASE_PATH=/resume for https://shanaty.ru/resume/. +const basePath = process.env.BASE_PATH ?? '/Emil_Shanayev_Resume' + /** @type {import('next').NextConfig} */ const nextConfig = { output: 'export', - basePath: '/Emil_Shanayev_Resume', - assetPrefix: '/Emil_Shanayev_Resume', + ...(basePath && { basePath, assetPrefix: basePath }), images: { unoptimized: true }, }