From 2036e909ea4460e159b006328cb8aede3e550e84 Mon Sep 17 00:00:00 2001 From: Dmitry Gammel Date: Sun, 26 Jul 2026 20:47:21 +0500 Subject: [PATCH] =?UTF-8?q?=D0=92=D1=91=D1=80=D1=81=D1=82=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=D1=8E=D0=BC=D0=B5=20=D0=B8=20=D0=BA=D0=B0?= =?UTF-8?q?=D1=80=D1=82=D0=BE=D1=87=D0=B5=D0=BA=20=D0=BF=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D0=B2=20=D0=B2=20=D1=82=D0=B5=D0=BC=D0=B5=20?= =?UTF-8?q?Matrix/cacto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Резюме: ASCII-кактус, glitch-заголовок, таймлайн опыта, скилл-теги, контакты. Проекты: карточки-терминалы (home_automatization, cactoz.su) со ссылками на git.cactoz.su. Canvas-дождь на цифровых/катакана символах, пиксельный SVG-кактус как лого/favicon. --- frontend/assets/cactus-logo.svg | 8 ++ frontend/css/projects.css | 64 +++++++++++- frontend/css/resume.css | 102 +++++++++++++++++- frontend/css/theme.css | 180 +++++++++++++++++++++++++++++++- frontend/game.html | 31 ++++-- frontend/index.html | 135 ++++++++++++++++++++++-- frontend/js/matrix-rain.js | 46 ++++++-- frontend/js/projects.js | 40 ++++++- frontend/projects.html | 35 +++++-- 9 files changed, 603 insertions(+), 38 deletions(-) create mode 100644 frontend/assets/cactus-logo.svg diff --git a/frontend/assets/cactus-logo.svg b/frontend/assets/cactus-logo.svg new file mode 100644 index 0000000..efb6fd1 --- /dev/null +++ b/frontend/assets/cactus-logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/css/projects.css b/frontend/css/projects.css index 56b3df1..1630e1e 100644 --- a/frontend/css/projects.css +++ b/frontend/css/projects.css @@ -1 +1,63 @@ -/* TODO: карточки проектов — этап 2 */ +.project-grid { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.5rem; +} + +.project-card { + border: var(--border); + background: var(--color-bg-raised); + display: flex; + flex-direction: column; +} + +.project-card__bar { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + border-bottom: var(--border); + color: var(--color-silver); + font-size: 0.8rem; +} + +.project-card__bar span { + width: 9px; + height: 9px; + border-radius: 50%; + background: var(--color-accent-dim); + opacity: 0.6; + flex-shrink: 0; +} + +.project-card__body { + padding: 1.25rem 1.5rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + flex: 1; +} + +.project-card__title { + margin: 0; + color: var(--color-accent); + font-size: 1.05rem; +} + +.project-card__desc { + margin: 0; + color: var(--color-fg); + font-size: 0.9rem; +} + +.project-card__link { + margin-top: auto; +} + +.project-card__link::before { + content: '$ '; + color: var(--color-accent-dim); +} diff --git a/frontend/css/resume.css b/frontend/css/resume.css index 076bfc4..5715efc 100644 --- a/frontend/css/resume.css +++ b/frontend/css/resume.css @@ -1 +1,101 @@ -/* TODO: вёрстка резюме — этап 2 */ +.hero { + display: flex; + align-items: center; + gap: 2.5rem; + padding: 2.5rem 0; + flex-wrap: wrap; +} + +.hero-text h1 { + margin: 0 0 0.4rem; + font-size: 1.8rem; +} + +.hero-text .role { + margin: 0; + color: var(--color-accent); +} + +.hero-text .tagline { + margin: 0.75rem 0 0; + color: var(--color-silver); + max-width: 34ch; +} + +section { + margin: 3rem 0; +} + +section > h2 { + font-size: 1rem; + font-weight: 400; + margin-bottom: 1.25rem; +} + +.about p { + max-width: 68ch; +} + +.timeline { + border-left: var(--border); + margin: 0; + padding: 0 0 0 1.5rem; + list-style: none; +} + +.timeline-item { + position: relative; + margin-bottom: 2rem; +} + +.timeline-item::before { + content: ''; + position: absolute; + left: -1.6rem; + top: 0.4rem; + width: 8px; + height: 8px; + background: var(--color-accent); + box-shadow: 0 0 6px var(--color-accent-dim); +} + +.timeline-item__meta { + color: var(--color-silver); + font-size: 0.85rem; +} + +.timeline-item__role { + color: var(--color-accent); + margin: 0.15rem 0 0.5rem; + font-size: 1rem; +} + +.timeline-item ul { + margin: 0; + padding-left: 1.2rem; +} + +.timeline-item li::marker { + color: var(--color-accent-dim); +} + +.contact-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.contact-list a::before { + content: '$ '; + color: var(--color-accent-dim); +} + +@media (max-width: 640px) { + .hero { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/frontend/css/theme.css b/frontend/css/theme.css index 60895b6..6c6207b 100644 --- a/frontend/css/theme.css +++ b/frontend/css/theme.css @@ -1,10 +1,12 @@ :root { --color-bg: #0a0e0c; + --color-bg-raised: #0f1512; --color-fg: #c7c7c7; --color-accent: #00ff41; --color-accent-dim: #0a8f2c; --color-silver: #9fa3a0; --font-mono: 'JetBrains Mono', 'Share Tech Mono', ui-monospace, monospace; + --border: 1px solid #163020; } * { @@ -13,26 +15,196 @@ html, body { margin: 0; - height: 100%; + min-height: 100%; } body { background: var(--color-bg); color: var(--color-fg); font-family: var(--font-mono); + line-height: 1.5; +} + +::selection { + background: var(--color-accent-dim); + color: var(--color-bg); } a { color: var(--color-accent); + text-decoration: none; } +a:hover { + text-decoration: underline; +} + +a:focus-visible, +button:focus-visible { + outline: 1px solid var(--color-accent); + outline-offset: 2px; +} + +.container { + max-width: 860px; + margin: 0 auto; + padding: 2.5rem 1.5rem 4rem; +} + +/* --- фон: canvas цифрового дождя --- */ +#matrix-rain { + position: fixed; + inset: 0; + z-index: -2; + pointer-events: none; +} + +/* лёгкие сканлайны поверх дождя, под контентом */ +body::before { + content: ''; + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + background: repeating-linear-gradient( + to bottom, + rgba(0, 0, 0, 0.15) 0px, + rgba(0, 0, 0, 0.15) 1px, + transparent 1px, + transparent 3px + ); +} + +/* --- шапка --- */ +.site-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding-bottom: 1.5rem; + border-bottom: var(--border); + flex-wrap: wrap; +} + +.logo { + display: flex; + align-items: center; + gap: 0.6rem; + color: var(--color-accent); + font-weight: 600; + letter-spacing: 0.02em; +} + +.logo svg { + width: 20px; + height: 20px; + filter: drop-shadow(0 0 4px var(--color-accent-dim)); +} + +.nav-links { + display: flex; + gap: 1.5rem; + list-style: none; + margin: 0; + padding: 0; +} + +.nav-links a { + color: var(--color-fg); +} + +.nav-links a:hover, +.nav-links a[aria-current='page'] { + color: var(--color-accent); +} + +/* --- терминальный префикс перед заголовками --- */ .prompt::before { content: 'cacto@matrix:~$ '; color: var(--color-accent-dim); } -#matrix-rain { - position: fixed; +/* --- терминальное окно-обёртка для блоков контента --- */ +.term-window { + border: var(--border); + background: var(--color-bg-raised); + margin: 1.75rem 0; +} + +.term-window__bar { + display: flex; + gap: 0.4rem; + padding: 0.5rem 0.75rem; + border-bottom: var(--border); +} + +.term-window__bar span { + width: 9px; + height: 9px; + border-radius: 50%; + background: var(--color-accent-dim); + opacity: 0.6; +} + +.term-window__body { + padding: 1.25rem 1.5rem; +} + +/* --- ASCII-арт --- */ +.ascii-cactus { + color: var(--color-accent); + font-size: 0.85rem; + line-height: 1.15; + margin: 0; + text-shadow: 0 0 6px var(--color-accent-dim); +} + +/* --- glitch-эффект для акцентного текста --- */ +.glitch { + position: relative; + display: inline-block; +} + +.glitch::before, +.glitch::after { + content: attr(data-text); + position: absolute; inset: 0; - z-index: -1; + overflow: hidden; + opacity: 0.7; +} + +.glitch::before { + color: var(--color-silver); + clip-path: inset(0 0 55% 0); + animation: glitch-shift 3.4s infinite steps(2); +} + +.glitch::after { + color: var(--color-accent-dim); + clip-path: inset(55% 0 0 0); + animation: glitch-shift 2.6s infinite steps(2) reverse; +} + +@keyframes glitch-shift { + 0%, 92%, 100% { transform: translate(0, 0); } + 94% { transform: translate(-2px, 1px); } + 96% { transform: translate(2px, -1px); } +} + +/* --- теги (скиллы, стек проекта) --- */ +.tag-list { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + list-style: none; + margin: 0; + padding: 0; +} + +.tag { + border: 1px solid var(--color-accent-dim); + color: var(--color-accent); + padding: 0.15rem 0.6rem; + font-size: 0.8rem; } diff --git a/frontend/game.html b/frontend/game.html index 98aaaaa..5c55e48 100644 --- a/frontend/game.html +++ b/frontend/game.html @@ -4,16 +4,35 @@ ./battlecity — cacto + -
- -

./battlecity

-
-
-
+ + +
+ + +
+ +

./battlecity

+
+
+
+
+ + diff --git a/frontend/index.html b/frontend/index.html index 30cc291..f9b4488 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,21 +3,136 @@ - cacto — Гаммель Дмитрий + cacto — Гаммель Дмитрий, PHP/Go backend + + -
- -

whoami

-

Гаммель Дмитрий Викторович — PHP/Go backend, опыт КИПиА/АСУТП

- -
+ +
+ + +
+
+ +
+

Гаммель Дмитрий

+

PHP / Go backend-разработчик

+

Живучий, колючий, не требует много воды. Специализация — сложные запросы, legacy-код, архитектура.

+
+
+ +
+

cat about.txt

+
+
+
+

+ Backend-разработчик с 6-летним коммерческим опытом на PHP: сложные запросы к базам данных, + работа с legacy-кодом, архитектурные решения. Спроектировал аналитический контур на ClickHouse, + ускоривший формирование отчётности в 15–20 раз; неофициально возглавлял разработку модуля + документооборота в банковской IT-инфраструктуре. +

+

+ Начинал с электротехники и АСУТП — писал управляющий софт для автоматизированных систем. + Путь «от электрического сигнала до строчки кода» помогает быстро разбираться в сложных + legacy-системах и нестандартных задачах. +

+

+ Сейчас изучаю Go и продолжаю искать позицию Middle+/Senior backend-разработчика. +

+
+
+
+ +
+

ls -la experience/

+
    +
  1. +
    июль 2022 — март 2026 · Екатеринбург
    +

    PHP-разработчик — ООО «ЭТП ГПБ» (дочернее АО «Газпромбанк»)

    +
      +
    • Поддерживал legacy на Zend Framework, участвовал в переходе на Symfony; PHP 7.4 → 8.0.
    • +
    • Спроектировал аналитический контур отчётности на ClickHouse — сократил время формирования отчётов с 3–4 минут до 10–15 секунд.
    • +
    • Интеграции с внутренними банковскими сервисами (проверка ЭП, машиночитаемые доверенности), очереди RabbitMQ.
    • +
    • Неофициально возглавлял разработку нового модуля документооборота: архитектура, код-ревью, MVP.
    • +
    +
  2. +
  3. +
    март 2021 — июль 2022 · фриланс/субподряд
    +

    PHP-разработчик — kupisever.ru

    +
      +
    • Доработка бэкенда B2B-платформы (доска объявлений) на Yii2, стек PHP/PostgreSQL/RabbitMQ/Docker.
    • +
    • Модуль email-рассылок с очередями сообщений и конструктором писем.
    • +
    • Модуль категорий объявлений — админка и клиентское меню.
    • +
    +
  4. +
  5. +
    июль 2020 — март 2021 · Курган
    +

    PHP-разработчик (с обязанностями руководителя группы) — ИстВуд

    +
      +
    • Интернет-магазины на 1С-Битрикс, интеграции с 1С и платёжными системами.
    • +
    • Фактически руководил командой из 3 разработчиков: задачи, код-ревью, технические собеседования.
    • +
    +
  6. +
+
+ +
+

skills --list

+
    +
  • PHP8
  • +
  • Symfony
  • +
  • Zend Framework
  • +
  • Yii2
  • +
  • PostgreSQL
  • +
  • MySQL
  • +
  • ClickHouse
  • +
  • RabbitMQ
  • +
  • Docker
  • +
  • REST API
  • +
  • PHPUnit
  • +
  • ООП
  • +
  • Go (изучаю)
  • +
+
+ +
+

cat contact.txt

+ +
+
+
+ diff --git a/frontend/js/matrix-rain.js b/frontend/js/matrix-rain.js index 39ce165..814b197 100644 --- a/frontend/js/matrix-rain.js +++ b/frontend/js/matrix-rain.js @@ -1,11 +1,45 @@ -// TODO: полноценный canvas-эффект цифрового дождя — этап 2 const canvas = document.getElementById('matrix-rain'); -if (canvas) { + +if (canvas && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) { const ctx = canvas.getContext('2d'); - const resize = () => { + const chars = 'アカサタナハマヤラワ0123456789ABCDEFcacto$#@%&*'.split(''); + const fontSize = 15; + const bg = getComputedStyle(document.documentElement).getPropertyValue('--color-bg').trim(); + const accent = getComputedStyle(document.documentElement).getPropertyValue('--color-accent').trim(); + const silver = getComputedStyle(document.documentElement).getPropertyValue('--color-silver').trim(); + + let columns = 0; + let drops = []; + + function setup() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; - }; - resize(); - window.addEventListener('resize', resize); + columns = Math.floor(canvas.width / fontSize); + drops = new Array(columns).fill(0).map(() => Math.floor((Math.random() * canvas.height) / fontSize)); + } + + function draw() { + ctx.fillStyle = `${bg}cc`; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.font = `${fontSize}px monospace`; + + for (let i = 0; i < columns; i++) { + const char = chars[Math.floor(Math.random() * chars.length)]; + const x = i * fontSize; + const y = drops[i] * fontSize; + + ctx.fillStyle = Math.random() > 0.98 ? silver : accent; + ctx.fillText(char, x, y); + + if (y > canvas.height && Math.random() > 0.975) { + drops[i] = 0; + } else { + drops[i]++; + } + } + } + + setup(); + window.addEventListener('resize', setup); + setInterval(draw, 50); } diff --git a/frontend/js/projects.js b/frontend/js/projects.js index b439d30..b056dc2 100644 --- a/frontend/js/projects.js +++ b/frontend/js/projects.js @@ -1 +1,39 @@ -// TODO: рендер карточек проектов из данных — этап 2 +const projects = [ + { + name: 'home_automatization', + title: 'Home Automation Platform', + description: + 'Микросервисная платформа умного дома на Go и Laravel. Первая зона — гроубокс (полив, свет, климат), дальше — весь дом.', + tags: ['Go', 'Laravel', 'Docker', 'RabbitMQ', 'ClickHouse', 'gRPC', 'MQTT'], + url: 'https://git.cactoz.su/cacto/home_automatization', + }, + { + name: 'cactoz.su', + title: 'cactoz.su — этот сайт', + description: + 'Личный сайт-визитка: резюме, проекты и мультиплеерный клон BattleCity. Тема — Матрица.', + tags: ['HTML/CSS/JS', 'C++', 'uWebSockets', 'Quintus.js', 'nginx', 'Docker'], + url: 'https://git.cactoz.su/cacto/cactoz.su', + }, +]; + +const list = document.getElementById('project-list'); + +if (list) { + list.innerHTML = projects + .map( + (p) => ` +
  • +
    ${p.name}/README.md
    +
    +

    ${p.title}

    +

    ${p.description}

    +
      + ${p.tags.map((t) => `
    • ${t}
    • `).join('')} +
    + ${p.url.replace('https://', '')} +
    +
  • ` + ) + .join(''); +} diff --git a/frontend/projects.html b/frontend/projects.html index 378c96d..01703af 100644 --- a/frontend/projects.html +++ b/frontend/projects.html @@ -4,19 +4,36 @@ ls projects/ — cacto + + -
    - -

    ls projects/

    - - -
    + + +
    + + +
    +
    +

    ls projects/

    +
      +
      +
      +
      + +