- og:title/description/image + canonical + twitter:card в index.html, нарисован assets/og.png (1200x630, тема сайта). CSP не трогали — картинка того же происхождения. - Переписан текст «обо мне» и формулировка про текущую работу в Уралэнергоаква (убраны двусмысленности "неофициально возглавлял" и "продолжаю искать"), Go убран из title/skills-тега как отдельная "изучаю" пометка — теперь просто в стеке, раз есть реальный Go-код в home_automatization. - resume.js/projects.js больше не перерисовывают контент через JS при языке по умолчанию (ru) — index.html/projects.html содержат готовый серверный HTML, JS перерисовывает только при переключении на EN. Раньше весь текст резюме подгружался пустым в #resume-root, curl без JS ничего не видел. - Добавлены кнопка «скачать резюме (PDF)» и ссылка на git.cactoz.su в первый экран резюме. - home_automatization на /projects честно помечен «в разработке» вместо описания как готового продукта. - Игра переименована из BattleCity (товарный знак Namco) в ./tanks, убрана из основного меню во все страницы — ссылка теперь только в подвале страницы whoami.
283 lines
20 KiB
JavaScript
283 lines
20 KiB
JavaScript
import { getLang, initLangToggle } from './i18n.js';
|
||
|
||
const DATA = {
|
||
ru: {
|
||
name: 'Гаммель Дмитрий',
|
||
title: 'PHP-разработчик (Backend, Middle+/Senior)',
|
||
tagline: 'От электрического сигнала до строчки кода.',
|
||
statusLabel: 'STATUS: OPEN TO WORK',
|
||
aboutHeading: 'cat about.txt',
|
||
about: [
|
||
'Backend-разработчик, 6 лет коммерческого опыта на PHP. Работаю с legacy, сложными запросами и архитектурными решениями — там, где «просто добавить фичу» не получается.',
|
||
'Спроектировал аналитический контур на ClickHouse: отчёты формировались 3–4 минуты, стали за 10–15 секунд. Вёл разработку модуля документооборота в банковской IT-инфраструктуре — архитектура, декомпозиция, код-ревью, онбординг разработчиков.',
|
||
'Начинал с электротехники и АСУТП: писал управляющий софт для промышленных систем на ST, поднимал SCADA. Путь «от электрического сигнала до строчки кода» помогает быстро вникать в чужие системы и в задачи на стыке железа и бэкенда.',
|
||
'Ищу позицию Middle+/Senior backend. Основной стек — PHP, параллельно пишу на Go.',
|
||
],
|
||
experienceHeading: 'ls -la experience/',
|
||
experience: [
|
||
{
|
||
period: 'март 2026 — настоящее время', duration: '5 месяцев', location: 'Екатеринбург',
|
||
role: 'Программист АСУТП и КИПиА', company: 'Уралэнергоаква',
|
||
bullets: ['Временный период вне основной специальности после завершения проекта в ЭТП ГПБ. Ищу позицию backend-разработчика; параллельно — пет-проекты и Go.'],
|
||
},
|
||
{
|
||
period: 'июль 2022 — март 2026', duration: '3 года 9 месяцев', location: 'Екатеринбург',
|
||
role: 'PHP-разработчик', company: 'ООО «ЭТП ГПБ» (дочернее АО «Газпромбанк»)',
|
||
bullets: [
|
||
'Разрабатывал и поддерживал внутреннюю систему электронного документооборота для согласования и контроля исполнения договоров.',
|
||
'Поддерживал и развивал legacy-проект на Zend Framework, участвовал в архитектурном переходе на Symfony; PHP 7.4 → 8.0.',
|
||
'Переписывал построители запросов на «чистый» SQL с оптимизацией под PostgreSQL, внедрял сервисный слой архитектуры.',
|
||
'Спроектировал и реализовал аналитический контур отчётности на ClickHouse — сократил время формирования отчётов с 3–4 минут до 10–15 секунд.',
|
||
'Реализовал интеграции с внутренними банковскими сервисами, включая проверку электронной подписи и машиночитаемых доверенностей.',
|
||
'Работал с очередями сообщений (RabbitMQ) для асинхронной обработки задач.',
|
||
'Неофициально возглавлял разработку нового модуля документооборота: планировал архитектуру, проводил код-ревью, довёл проект до MVP.',
|
||
'Работал по Scrum: месячные спринты, таск-трекер YouTrack, оценка задач методом Planning Poker.',
|
||
],
|
||
},
|
||
{
|
||
period: 'март 2021 — июль 2022', duration: '1 год 5 месяцев', location: 'удалённо',
|
||
role: 'PHP-разработчик', company: 'фриланс/субподряд · kupisever.ru',
|
||
bullets: [
|
||
'Дорабатывал бэкенд действующей B2B-платформы (доска объявлений) на Yii2 в качестве субподрядчика, работал самостоятельно на удалённом проекте. Стек: PHP, Yii2, PostgreSQL, RabbitMQ, Docker.',
|
||
'Разработал модуль email-рассылок с очередями сообщений (RabbitMQ): веб-форма конструктора писем с текстовым редактором и гибкой настройкой параметров рассылки.',
|
||
'Реализовал модуль категорий объявлений — административную часть (бэкенд) и клиентское меню (фронтенд).',
|
||
],
|
||
},
|
||
{
|
||
period: 'июль 2020 — март 2021', duration: '9 месяцев', location: 'Курган',
|
||
role: 'PHP-разработчик (с обязанностями руководителя группы)', company: 'ИстВуд',
|
||
bullets: [
|
||
'Интернет-магазины на 1С-Битрикс: вёрстка, интеграция готовых модулей, доработка бизнес-логики под задачи клиентов.',
|
||
'Реализовал интеграции с 1С (обмен товарными каталогами и заказами) и платёжными системами (эквайринг).',
|
||
'Реализовал нестандартный механизм автоматического получения и обновления каталога товаров (несколько тысяч позиций, ежесуточное обновление) для интернет-магазина на кастомном шаблоне вне типовых решений Bitrix.',
|
||
'Разработал функционал онлайн-записи на приём к врачу для сайта частной клиники; реализовал интеграцию с фискальным регистратором.',
|
||
'Участвовал во внедрении Битрикс24: настройка бизнес-процессов и цепочек согласования закупок.',
|
||
'Фактически руководил командой из 3 разработчиков: распределял задачи, контролировал качество реализации, проводил технические собеседования.',
|
||
],
|
||
},
|
||
{
|
||
period: 'октябрь 2019 — февраль 2020', duration: '5 месяцев', location: 'Курган',
|
||
role: 'Техник по телекоммуникациям', company: 'Урал-М',
|
||
bullets: ['Настройка и обслуживание систем СКУД, видеонаблюдения, радиоканальной связи; работа с 1С.'],
|
||
},
|
||
{
|
||
period: 'апрель 2014 — июль 2018', duration: '4 года 4 месяца', location: 'Курган',
|
||
role: 'Техник-электрик', company: 'Кирпичный завод, Мясокомбинат, Хлебозавод',
|
||
bullets: ['Электромонтажные и наладочные работы на производственных предприятиях.'],
|
||
},
|
||
],
|
||
skillsHeading: 'skills --list',
|
||
skillGroups: [
|
||
{ title: 'backend', items: ['PHP8', 'PHP7', 'Symfony', 'Zend Framework', 'Yii2', 'PostgreSQL', 'MySQL', 'ClickHouse', 'SQL', 'RabbitMQ', 'Docker', 'Git', 'REST API', 'PHPUnit', 'Composer', 'ООП', 'Go'] },
|
||
{ title: 'electrical / automation', items: ['АСУТП', 'КИПиА', 'СКУД', 'Видеонаблюдение', 'Радиоканальная связь', 'Электромонтаж', '1С'] },
|
||
{ title: 'soft', items: ['Оптимизация SQL-запросов', 'Обучение и адаптация junior-разработчиков', 'Быстрая обучаемость'] },
|
||
],
|
||
educationHeading: 'cat education.txt',
|
||
education: [
|
||
{ year: '2013', degree: 'Неоконченное высшее', school: 'Тюменский государственный нефтегазовый университет (ТюмГНГУ)', detail: 'Институт кибернетики, информатики и связи, Электроэнергетика и электротехника' },
|
||
{ year: '2011', degree: 'Свидетельство о квалификации «Электромонтёр 3 разряда»', school: 'ФГБОУ ВПО «Тюменский государственный университет»', detail: '' },
|
||
],
|
||
contactHeading: 'cat contact.txt',
|
||
contact: { email: 'cactozzz93@gmail.com', telegram: 't.me/Cactozz', telegramUrl: 'https://t.me/Cactozz' },
|
||
resumeLabel: 'скачать резюме (PDF)',
|
||
giteaLabel: 'git.cactoz.su/cacto',
|
||
footerHtml: 'P.S. есть ещё <a href="/game">./tanks</a> — мультиплеерная мини-игра, если будет пара свободных минут.',
|
||
},
|
||
en: {
|
||
name: 'Dmitry Gammel',
|
||
title: 'PHP Developer (Backend, Middle+/Senior)',
|
||
tagline: 'From electrical signal to line of code.',
|
||
statusLabel: 'STATUS: OPEN TO WORK',
|
||
aboutHeading: 'cat about.txt',
|
||
about: [
|
||
"Backend developer, 6 years of commercial PHP experience. I work with legacy code, complex queries, and architecture — the cases where \"just add a feature\" doesn't cut it.",
|
||
"Designed a ClickHouse analytics pipeline: reports went from 3–4 minutes to 10–15 seconds. Led development of a document-workflow module in a bank's IT infrastructure — architecture, decomposition, code review, onboarding developers.",
|
||
'Started in electrical engineering and industrial automation: wrote control software for industrial systems in ST, built SCADA systems. The path from electrical signal to line of code helps me get up to speed fast in unfamiliar systems and problems at the intersection of hardware and backend.',
|
||
'Looking for a Middle+/Senior backend position. Main stack is PHP, with Go on the side.',
|
||
],
|
||
experienceHeading: 'ls -la experience/',
|
||
experience: [
|
||
{
|
||
period: 'Mar 2026 — present', duration: '5 months', location: 'Yekaterinburg',
|
||
role: 'ACS/KIPiA Programmer (industrial automation)', company: 'Uralenergoakva',
|
||
bullets: ["A temporary role outside my core specialty after wrapping up the ETP GPB project. Looking for a backend position; alongside that — side projects and Go."],
|
||
},
|
||
{
|
||
period: 'Jul 2022 — Mar 2026', duration: '3 years 9 months', location: 'Yekaterinburg',
|
||
role: 'PHP Developer', company: 'ETP GPB LLC (subsidiary of Gazprombank)',
|
||
bullets: [
|
||
'Built and maintained an internal document-workflow system for contract approval and execution tracking.',
|
||
'Maintained a legacy Zend Framework project and took part in the architectural move to Symfony; PHP 7.4 → 8.0.',
|
||
'Rewrote query builders as raw SQL optimized for PostgreSQL, introduced a service-layer architecture.',
|
||
'Designed and built a ClickHouse analytics pipeline — cut report generation time from 3–4 minutes to 10–15 seconds.',
|
||
'Built integrations with internal banking services, including e-signature and machine-readable power-of-attorney verification.',
|
||
'Worked with message queues (RabbitMQ) for async task processing.',
|
||
'Informally led development of a new document-workflow module: planned the architecture, ran code review, took it to MVP.',
|
||
'Worked in Scrum: monthly sprints, YouTrack, Planning Poker estimation.',
|
||
],
|
||
},
|
||
{
|
||
period: 'Mar 2021 — Jul 2022', duration: '1 year 5 months', location: 'Remote',
|
||
role: 'PHP Developer', company: 'Freelance / subcontract · kupisever.ru',
|
||
bullets: [
|
||
'Worked as a subcontractor on the backend of a live B2B classifieds platform on Yii2, working independently on a remote project. Stack: PHP, Yii2, PostgreSQL, RabbitMQ, Docker.',
|
||
'Built an email-campaign module with message queues (RabbitMQ): a web-based email builder with a text editor and flexible send settings.',
|
||
'Built the listing-categories module — both the admin backend and the client-facing menu.',
|
||
],
|
||
},
|
||
{
|
||
period: 'Jul 2020 — Mar 2021', duration: '9 months', location: 'Kurgan',
|
||
role: 'PHP Developer (acting team lead)', company: 'IstWood',
|
||
bullets: [
|
||
'Built and maintained online stores on 1C-Bitrix: markup, module integration, business-logic customization.',
|
||
'Built integrations with 1C (catalog and order exchange) and payment systems (acquiring).',
|
||
"Built a custom mechanism for automatically fetching and updating a product catalog (several thousand SKUs, daily updates) on a custom template outside Bitrix's standard tooling.",
|
||
'Built online doctor-appointment booking for a private clinic site; integrated a fiscal register for automatic receipts.',
|
||
'Took part in rolling out Bitrix24: configured business processes and procurement approval chains.',
|
||
'Acting lead of a 3-developer team: assigned tasks, reviewed quality, ran technical interviews.',
|
||
],
|
||
},
|
||
{
|
||
period: 'Oct 2019 — Feb 2020', duration: '5 months', location: 'Kurgan',
|
||
role: 'Telecom Technician', company: 'Ural-M',
|
||
bullets: ['Set up and maintained access-control, video-surveillance and radio-link systems; worked with 1C.'],
|
||
},
|
||
{
|
||
period: 'Apr 2014 — Jul 2018', duration: '4 years 4 months', location: 'Kurgan',
|
||
role: 'Electrical Technician', company: 'Brick factory, meat plant, bakery',
|
||
bullets: ['Electrical installation and commissioning work at manufacturing plants.'],
|
||
},
|
||
],
|
||
skillsHeading: 'skills --list',
|
||
skillGroups: [
|
||
{ title: 'backend', items: ['PHP8', 'PHP7', 'Symfony', 'Zend Framework', 'Yii2', 'PostgreSQL', 'MySQL', 'ClickHouse', 'SQL', 'RabbitMQ', 'Docker', 'Git', 'REST API', 'PHPUnit', 'Composer', 'OOP', 'Go'] },
|
||
{ title: 'electrical / automation', items: ['Industrial automation (ACS)', 'KIPiA', 'Access control', 'Video surveillance', 'Radio links', 'Electrical installation', '1C'] },
|
||
{ title: 'soft', items: ['SQL query optimization', 'Mentoring junior developers', 'Fast learner'] },
|
||
],
|
||
educationHeading: 'cat education.txt',
|
||
education: [
|
||
{ year: '2013', degree: 'Incomplete higher education', school: 'Tyumen State Oil and Gas University', detail: 'Institute of Cybernetics, Informatics and Communications — Power Engineering and Electrical Engineering' },
|
||
{ year: '2011', degree: 'Qualification certificate, Electrician grade 3', school: 'Tyumen State University', detail: '' },
|
||
],
|
||
contactHeading: 'cat contact.txt',
|
||
contact: { email: 'cactozzz93@gmail.com', telegram: 't.me/Cactozz', telegramUrl: 'https://t.me/Cactozz' },
|
||
resumeLabel: 'download résumé (PDF)',
|
||
giteaLabel: 'git.cactoz.su/cacto',
|
||
footerHtml: 'P.S. there\'s also <a href="/game">./tanks</a> — a small multiplayer game, if you have a couple of minutes.',
|
||
},
|
||
};
|
||
|
||
const ASCII_CACTUS = ` , ,
|
||
|\\_/|
|
||
.--| |--.
|
||
( | | )
|
||
\`--| |--'
|
||
| |
|
||
.--| |--.
|
||
( | | )
|
||
\`--| |--'
|
||
| |
|
||
| |
|
||
__| |__
|
||
(_________)`;
|
||
|
||
function renderExperience(list) {
|
||
return list
|
||
.map(
|
||
(job) => `
|
||
<li class="timeline-item">
|
||
<div class="timeline-item__meta">${job.period} · ${job.duration}</div>
|
||
<h3 class="timeline-item__role">${job.role}</h3>
|
||
<div class="timeline-item__company">${job.company} · ${job.location}</div>
|
||
<ul>${job.bullets.map((b) => `<li><span class="dash">-</span><span>${b}</span></li>`).join('')}</ul>
|
||
</li>`
|
||
)
|
||
.join('');
|
||
}
|
||
|
||
function renderSkillGroups(groups) {
|
||
return groups
|
||
.map(
|
||
(g) => `
|
||
<div class="skill-group">
|
||
<div class="skill-group__title">${g.title}</div>
|
||
<ul class="tag-list">${g.items.map((i) => `<li class="tag">${i}</li>`).join('')}</ul>
|
||
</div>`
|
||
)
|
||
.join('');
|
||
}
|
||
|
||
function renderEducation(list) {
|
||
return list
|
||
.map(
|
||
(e) => `
|
||
<li class="edu-item">
|
||
<div class="edu-item__year">${e.year}</div>
|
||
<div class="edu-item__degree">${e.degree}</div>
|
||
<div class="edu-item__school">${e.school}</div>
|
||
${e.detail ? `<div class="edu-item__detail">${e.detail}</div>` : ''}
|
||
</li>`
|
||
)
|
||
.join('');
|
||
}
|
||
|
||
function render() {
|
||
const t = DATA[getLang()];
|
||
document.title = `cacto — ${t.name}, ${t.title}`;
|
||
|
||
document.getElementById('resume-root').innerHTML = `
|
||
<section class="hero">
|
||
<pre class="ascii-cactus" aria-hidden="true">${ASCII_CACTUS}</pre>
|
||
<div class="hero-text">
|
||
<div class="status-line"><span class="status-dot"></span>${t.statusLabel}</div>
|
||
<h1><span class="glitch" data-text="${t.name}">${t.name}</span></h1>
|
||
<p class="role">${t.title}</p>
|
||
<p class="tagline">${t.tagline}</p>
|
||
<div class="hero-actions">
|
||
<a class="hero-actions__btn" href="/assets/dmitry-gammel-cv.pdf" download>${t.resumeLabel}</a>
|
||
<a class="hero-actions__btn" href="https://git.cactoz.su/cacto" target="_blank" rel="noopener">${t.giteaLabel}</a>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="about">
|
||
<h2 class="section-heading"><span class="section-heading__num">01</span>${t.aboutHeading}</h2>
|
||
${t.about.map((p) => `<p>${p}</p>`).join('')}
|
||
</section>
|
||
|
||
<section class="experience">
|
||
<h2 class="section-heading"><span class="section-heading__num">02</span>${t.experienceHeading}</h2>
|
||
<ol class="timeline">${renderExperience(t.experience)}</ol>
|
||
</section>
|
||
|
||
<section class="skills">
|
||
<h2 class="section-heading"><span class="section-heading__num">03</span>${t.skillsHeading}</h2>
|
||
${renderSkillGroups(t.skillGroups)}
|
||
</section>
|
||
|
||
<section class="education">
|
||
<h2 class="section-heading"><span class="section-heading__num">04</span>${t.educationHeading}</h2>
|
||
<ul class="edu-list">${renderEducation(t.education)}</ul>
|
||
</section>
|
||
|
||
<section class="contact">
|
||
<h2 class="section-heading"><span class="section-heading__num">05</span>${t.contactHeading}</h2>
|
||
<ul class="contact-list">
|
||
<li><span class="contact-list__label">email</span><a href="mailto:${t.contact.email}">${t.contact.email}</a></li>
|
||
<li><span class="contact-list__label">telegram</span><a href="${t.contact.telegramUrl}">${t.contact.telegram}</a></li>
|
||
</ul>
|
||
</section>`;
|
||
|
||
const footer = document.getElementById('site-footer');
|
||
if (footer) footer.innerHTML = `<p>${t.footerHtml}</p>`;
|
||
}
|
||
|
||
// index.html уже содержит серверный RU-рендер этой же разметки (для
|
||
// поисковиков и curl без JS) — перерисовываем через JS только когда
|
||
// нужен другой язык, чтобы не терять эту статику зря на каждой загрузке.
|
||
document.title = `cacto — ${DATA[getLang()].name}, ${DATA[getLang()].title}`;
|
||
if (getLang() !== 'ru') {
|
||
render();
|
||
}
|
||
initLangToggle(render);
|