diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..2c5541e --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,26 @@ +name: Deploy + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + # Runner уже на cacto-homelab и имеет доступ к docker.sock хоста, + # поэтому образ собирается прямо на месте — без registry и без SSH. + - name: Sync to deploy directory + run: | + mkdir -p /data/docker/compose/cactoz + rsync -a --delete --exclude '.git' ./ /data/docker/compose/cactoz/ + + - name: Build and restart + working-directory: /data/docker/compose/cactoz + run: | + docker compose -f docker-compose.prod.yml build + docker compose -f docker-compose.prod.yml up -d + docker image prune -f diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..43233e8 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,24 @@ +services: + nginx: + container_name: cactoz-nginx + build: + context: . + dockerfile: nginx/Dockerfile + networks: + - internal + - proxy + depends_on: + - gameserver + restart: unless-stopped + + gameserver: + build: ./gameserver + networks: + - internal + restart: unless-stopped + +networks: + internal: + # уже создана Caddy-стеком; сюда только подключаемся + proxy: + external: true 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/game.css b/frontend/css/game.css index 4e54213..b58023e 100644 --- a/frontend/css/game.css +++ b/frontend/css/game.css @@ -1 +1,90 @@ -/* TODO: лобби и игровое поле — этапы 3-6 */ +.lobby-panel { + border: var(--border); + background: var(--color-bg-raised); + padding: 1.25rem 1.5rem; + margin: 1.5rem 0; + max-width: 640px; +} + +.lobby-panel__row { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 1rem; + flex-wrap: wrap; +} + +.prompt-inline { + color: var(--color-accent-dim); +} + +.lobby-panel input { + background: var(--color-bg); + border: 1px solid var(--color-accent-dim); + color: var(--color-fg); + font-family: var(--font-mono); + padding: 0.3rem 0.5rem; +} + +.lobby-panel button { + background: transparent; + border: 1px solid var(--color-accent-dim); + color: var(--color-accent); + font-family: var(--font-mono); + padding: 0.35rem 0.75rem; + cursor: pointer; +} + +.lobby-panel button:hover:not(:disabled) { + border-color: var(--color-accent); + box-shadow: 0 0 6px var(--color-accent-dim); +} + +.lobby-panel button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.lobby-error { + color: var(--color-silver); + border-left: 2px solid var(--color-accent-dim); + padding-left: 0.6rem; + margin: 0 0 1rem; +} + +.room-list, +.room-players { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.room-list__item { + display: flex; + align-items: center; + gap: 0.75rem; + border-bottom: var(--border); + padding-bottom: 0.5rem; +} + +.room-list__name { + flex: 1; +} + +.room-list__count, +.room-list__state { + color: var(--color-silver); + font-size: 0.85rem; +} + +.room-list__empty { + color: var(--color-silver); +} + +.room-players li::before { + content: '- '; + color: var(--color-accent-dim); +} 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 @@