Initial commit
22
.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
node_modules
|
||||
.idea
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
38
README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||
35
eslint.config.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import js from '@eslint/js';
|
||||
import ts from 'typescript-eslint';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import globals from 'globals';
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs['flat/recommended'],
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: ts.parser
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: ['build/', '.svelte-kit/', 'dist/']
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
"svelte/no-at-html-tags": 0
|
||||
}
|
||||
}
|
||||
];
|
||||
4873
package-lock.json
generated
Normal file
41
package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "kopyl.dev.v2",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/material-symbols": "^1.2.1",
|
||||
"@iconify-json/mdi": "^1.2.0",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.5",
|
||||
"@sveltejs/enhanced-img": "^0.3.8",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@types/eslint": "^9.6.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-plugin-svelte": "^2.36.0",
|
||||
"globals": "^15.0.0",
|
||||
"postcss": "^8.4.47",
|
||||
"postcss-nested": "^6.2.0",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"vite": "^5.0.3"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"gsap": "^3.12.5",
|
||||
"sveltekit-i18n": "^2.4.2"
|
||||
}
|
||||
}
|
||||
7
postcss.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
"postcss-nested": {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
3
src/app.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
16
src/app.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
|
||||
import 'unplugin-icons/types/svelte'
|
||||
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
12
src/app.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
src/lib/assets/kopyl_frame_white.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/lib/assets/projects/games/1.png
Normal file
|
After Width: | Height: | Size: 350 KiB |
BIN
src/lib/assets/projects/games/2.png
Normal file
|
After Width: | Height: | Size: 693 KiB |
BIN
src/lib/assets/projects/games/3.png
Normal file
|
After Width: | Height: | Size: 1013 KiB |
BIN
src/lib/assets/projects/musanthrope/1.png
Normal file
|
After Width: | Height: | Size: 652 KiB |
BIN
src/lib/assets/projects/musanthrope/2.png
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
src/lib/assets/projects/pure_city/1.png
Normal file
|
After Width: | Height: | Size: 369 KiB |
BIN
src/lib/assets/projects/pure_city/2.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
src/lib/assets/projects/sleepy_care/1.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
src/lib/assets/projects/sleepy_care/2.png
Normal file
|
After Width: | Height: | Size: 657 KiB |
BIN
src/lib/assets/projects/sleepy_care/3.png
Normal file
|
After Width: | Height: | Size: 441 KiB |
BIN
src/lib/assets/projects/tpk_artel/1.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
src/lib/assets/projects/tpk_artel/2.png
Normal file
|
After Width: | Height: | Size: 865 KiB |
BIN
src/lib/assets/projects/tpk_artel/3.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
src/lib/assets/projects/vk_adult/1.png
Normal file
|
After Width: | Height: | Size: 418 KiB |
BIN
src/lib/assets/projects/vk_mute/1.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
20
src/lib/en.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"anatoly_kopyl": "Anatoly Kopyl",
|
||||
"hero": {
|
||||
"subtitle": "Attention to detail in development of non-standard solutions of any complexity"
|
||||
},
|
||||
"projects": {
|
||||
"h": "Projects",
|
||||
"link_types": {
|
||||
"website": "Website",
|
||||
"chrome": "Chrome Web Store",
|
||||
"github": "Github"
|
||||
},
|
||||
"pure_city": {
|
||||
"name": "Pure City"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"made_with": "Made with SvelteKit and Tailwind"
|
||||
}
|
||||
}
|
||||
3
src/lib/icons/Github.svelte
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" transform="scale(64)" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 973 B |
1
src/lib/icons/Linkedin.svelte
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="7.025 7.025 497.951 497.95"><path d="M256 7.025C118.494 7.025 7.025 118.494 7.025 256S118.494 504.975 256 504.975 504.976 393.506 504.976 256C504.975 118.494 393.504 7.025 256 7.025zm-66.427 369.343h-54.665V199.761h54.665v176.607zM161.98 176.633c-17.853 0-32.326-14.591-32.326-32.587 0-17.998 14.475-32.588 32.326-32.588s32.324 14.59 32.324 32.588c.001 17.997-14.472 32.587-32.324 32.587zm232.45 199.735h-54.4v-92.704c0-25.426-9.658-39.619-29.763-39.619-21.881 0-33.312 14.782-33.312 39.619v92.704h-52.43V199.761h52.43v23.786s15.771-29.173 53.219-29.173c37.449 0 64.257 22.866 64.257 70.169l-.001 111.825z" fill="currentColor"/></svg>
|
||||
|
After Width: | Height: | Size: 711 B |
4
src/lib/icons/Telegram.svelte
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 240.1 240.1">
|
||||
<circle fill-rule="evenodd" clip-rule="evenodd" fill="white" cx="120.1" cy="120.1" r="120.1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="black" d="M54.3,118.8c35-15.2,58.3-25.3,70-30.2 c33.3-13.9,40.3-16.3,44.8-16.4c1,0,3.2,0.2,4.7,1.4c1.2,1,1.5,2.3,1.7,3.3s0.4,3.1,0.2,4.7c-1.8,19-9.6,65.1-13.6,86.3 c-1.7,9-5,12-8.2,12.3c-7,0.6-12.3-4.6-19-9c-10.6-6.9-16.5-11.2-26.8-18c-11.9-7.8-4.2-12.1,2.6-19.1c1.8-1.8,32.5-29.8,33.1-32.3 c0.1-0.3,0.1-1.5-0.6-2.1c-0.7-0.6-1.7-0.4-2.5-0.2c-1.1,0.2-17.9,11.4-50.6,33.5c-4.8,3.3-9.1,4.9-13,4.8 c-4.3-0.1-12.5-2.4-18.7-4.4c-7.5-2.4-13.5-3.7-13-7.9C45.7,123.3,48.7,121.1,54.3,118.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 748 B |
45
src/lib/ru.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"anatoly_kopyl": "Анатолий Копыл",
|
||||
"hero": {
|
||||
"subtitle": "Разработка нестандартных решений любой сложности, внимание к деталям"
|
||||
},
|
||||
"projects": {
|
||||
"h": "Проекты",
|
||||
"link_types": {
|
||||
"website": "Сайт",
|
||||
"chrome": "Chrome Web Store",
|
||||
"github": "Github"
|
||||
},
|
||||
"pure_city": {
|
||||
"name": "Pure City",
|
||||
"description": "Сайт клининговой компании с высокими стандартами и широким спектром услуг."
|
||||
},
|
||||
"vk_mute": {
|
||||
"name": "VK Mute",
|
||||
"description": "Расширение для Chromium позволяющее скрывать нежелательные сообщения в групповых чатах Вконтакте."
|
||||
},
|
||||
"vk_adult": {
|
||||
"name": "VK Adult",
|
||||
"description": "Расширение для Chromium возвращающее небезопасный поиск."
|
||||
},
|
||||
"tpk_artel": {
|
||||
"name": "ТПК Артель",
|
||||
"description": "Сайт компании предоставляющей строительные услуги, услуги по монтажу, техническому обслуживанию и множество других."
|
||||
},
|
||||
"games": {
|
||||
"name": "Мои игры",
|
||||
"description": "Сайт с играми, которые я разрабатывал в прошлой жизни используя Lua. Некоторые из них я транспилировал на js и сделал доступными для игры из браузера."
|
||||
},
|
||||
"musanthrope": {
|
||||
"name": "Musanthrope x PAYDAY 2",
|
||||
"description": "Лендинг мода для PAYDAY 2 с мешапами треков от Musanthrope."
|
||||
},
|
||||
"sleepy_care": {
|
||||
"name": "Sleepy Care",
|
||||
"description": "Факты о сне, генератор фонового шума с возможностью делиться и выбирать звуки от сообщества, журнал снов."
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"made_with": "Сделано с SvelteKit и Tailwind"
|
||||
}
|
||||
}
|
||||
22
src/lib/translations.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import i18n from 'sveltekit-i18n';
|
||||
|
||||
const config = ({
|
||||
loaders: [
|
||||
{
|
||||
locale: 'en',
|
||||
key: '',
|
||||
loader: async () => (
|
||||
await import('./en.json')
|
||||
).default,
|
||||
},
|
||||
{
|
||||
locale: 'ru',
|
||||
key: '',
|
||||
loader: async () => (
|
||||
await import('./ru.json')
|
||||
).default,
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
export const { t, locale, locales, loading, loadTranslations } = new i18n(config);
|
||||
23
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import "../app.css";
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
||||
<style lang="postcss">
|
||||
:global(html) {
|
||||
background: theme(colors.slate.950);
|
||||
color: white;
|
||||
}
|
||||
|
||||
:global(a) {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: theme(colors.slate.500);
|
||||
text-underline-offset: 4px;
|
||||
transition: text-decoration-color .2s;
|
||||
|
||||
&:hover {
|
||||
text-decoration-color: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
13
src/routes/+layout.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type {LayoutLoad} from './$types';
|
||||
import {loadTranslations} from "$lib/translations";
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
export const load: LayoutLoad = async ({ url }) => {
|
||||
const { pathname } = url;
|
||||
|
||||
const initLocale = 'ru';
|
||||
|
||||
await loadTranslations(initLocale, pathname);
|
||||
return {};
|
||||
}
|
||||
13
src/routes/+page.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import Navbar from "./Navbar.svelte";
|
||||
import Hero from "./Hero.svelte";
|
||||
import Projects from "./Projects/Projects.svelte";
|
||||
import Footer from "./Footer.svelte";
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<Navbar></Navbar>
|
||||
<Hero></Hero>
|
||||
<Projects></Projects>
|
||||
<Footer></Footer>
|
||||
</main>
|
||||
16
src/routes/Footer.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import {t} from "$lib/translations";
|
||||
</script>
|
||||
|
||||
<footer class="border-t border-t-slate-800 p-4 md:px-8 flex gap-4 items-center justify-between">
|
||||
<div class="text-sm text-slate-400">
|
||||
{$t('footer.made_with')}
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="text-sm text-slate-400"
|
||||
href="https://v1.kopyl.dev"
|
||||
>
|
||||
Time Machine
|
||||
</a>
|
||||
</footer>
|
||||
138
src/routes/Hero.svelte
Normal file
@@ -0,0 +1,138 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte";
|
||||
import {t} from "$lib/translations";
|
||||
import Github from "$lib/icons/Github.svelte";
|
||||
import Telegram from "$lib/icons/Telegram.svelte";
|
||||
import Linkedin from "$lib/icons/Linkedin.svelte";
|
||||
import MaterialSymbolsStarRounded from '~icons/material-symbols/star-rounded';
|
||||
|
||||
let logo: HTMLElement;
|
||||
|
||||
onMount(async () => {
|
||||
const {gsap} = await import('gsap')
|
||||
const {Flip} = await import('gsap/Flip')
|
||||
const {ScrollTrigger} = await import('gsap/ScrollTrigger')
|
||||
|
||||
gsap.registerPlugin(Flip, ScrollTrigger);
|
||||
|
||||
ScrollTrigger.create({
|
||||
trigger: logo,
|
||||
start: "top-=10% top+=64px",
|
||||
scrub: 1,
|
||||
onEnter: () => {
|
||||
const state = Flip.getState(logo);
|
||||
logo.classList.add("hero__logo--in-nav")
|
||||
Flip.from(state, {
|
||||
duration: 1,
|
||||
})
|
||||
},
|
||||
onLeaveBack: () => {
|
||||
const state = Flip.getState(logo);
|
||||
logo.classList.remove("hero__logo--in-nav")
|
||||
Flip.from(state, {
|
||||
duration: 1,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const goTop = () => {
|
||||
document.body.scrollIntoView()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hero min-h-dvh flex flex-col justify-center gap-4 items-center p-2 md:p-8">
|
||||
<div
|
||||
class="hero__logo-wrapper mb-16"
|
||||
>
|
||||
<enhanced:img
|
||||
src="$lib/assets/kopyl_frame_white.png"
|
||||
alt="kopyl logo"
|
||||
class="hero__logo"
|
||||
bind:this={logo}
|
||||
on:click={goTop}
|
||||
on:keydown={goTop}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
></enhanced:img>
|
||||
</div>
|
||||
|
||||
<h1 class="hero__name text-4xl text-center">
|
||||
{$t('anatoly_kopyl')}
|
||||
</h1>
|
||||
|
||||
<p class="text-center mb-8 text-slate-400 max-w-96 text-balance">
|
||||
{@html $t('hero.subtitle')}
|
||||
</p>
|
||||
|
||||
<div class="flex gap-6">
|
||||
<a
|
||||
class="hero__social-icon"
|
||||
target="_blank"
|
||||
href="https://github.com/anatolykopyl"
|
||||
>
|
||||
<Github></Github>
|
||||
<div class="rounded-lg text-sm border border-slate-200 border-opacity-50 px-2 bg-slate-500 bg-opacity-25 absolute bottom-0 w-max translate-y-full mb-1 flex gap-1 items-center">
|
||||
123
|
||||
<MaterialSymbolsStarRounded class="max-h-4 max-w-4" />
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
class="hero__social-icon"
|
||||
target="_blank"
|
||||
href="https://www.linkedin.com/in/akopyl/"
|
||||
>
|
||||
<Linkedin></Linkedin>
|
||||
</a>
|
||||
<a
|
||||
class="hero__social-icon"
|
||||
target="_blank"
|
||||
href="https://t.me/anatolykopyl"
|
||||
>
|
||||
<Telegram></Telegram>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.hero__logo-wrapper {
|
||||
min-width: 64px;
|
||||
min-height: 64px;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
|
||||
.hero__logo {
|
||||
box-sizing: border-box;
|
||||
z-index: 100;
|
||||
background: theme(colors.slate.950);
|
||||
}
|
||||
|
||||
:global(.hero__logo--in-nav) {
|
||||
position: fixed;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
|
||||
@media screen(md) {
|
||||
left: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero__social-icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
height: fit-content;
|
||||
|
||||
:global(svg) {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: white;
|
||||
opacity: .8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
62
src/routes/Navbar.svelte
Normal file
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import MaterialSymbolsTranslateRounded from '~icons/material-symbols/translate-rounded';
|
||||
import {locale} from "$lib/translations";
|
||||
import {onMount} from "svelte";
|
||||
|
||||
let nav: HTMLElement;
|
||||
|
||||
const toggleLocale = () => {
|
||||
$locale = $locale === 'ru' ? 'en' : 'ru'
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const {gsap} = await import('gsap')
|
||||
const {ScrollTrigger} = await import('gsap/ScrollTrigger')
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
ScrollTrigger.create({
|
||||
trigger: '.hero__name',
|
||||
start: "top top+=97px",
|
||||
onEnter: () => {
|
||||
nav.classList.add('nav--with-border')
|
||||
},
|
||||
onLeaveBack: () => {
|
||||
nav.classList.remove('nav--with-border')
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<nav
|
||||
class="nav p-4 md:px-8 fixed top-0 w-full bg-slate-950 z-50 flex justify-between items-center"
|
||||
bind:this={nav}
|
||||
>
|
||||
<div
|
||||
class="nav__logo-spot"
|
||||
></div>
|
||||
|
||||
<button
|
||||
class="flex items-center justify-between gap-1 py-1 px-2 rounded border border-slate-600 h-fit w-16 transition hover:border-slate-400 uppercase"
|
||||
on:click={toggleLocale}
|
||||
>
|
||||
<MaterialSymbolsTranslateRounded/>
|
||||
{$locale}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<style lang="postcss">
|
||||
.nav {
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: border-bottom-color .3s;
|
||||
}
|
||||
|
||||
:global(.nav--with-border) {
|
||||
border-bottom-color: theme(colors.slate.800) !important;
|
||||
}
|
||||
|
||||
.nav__logo-spot {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
</style>
|
||||
65
src/routes/Projects/Project.svelte
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import {t} from "$lib/translations";
|
||||
import type {TProject} from "./projects";
|
||||
|
||||
export let project: TProject;
|
||||
|
||||
const visifyImage = (event: MouseEvent) => {
|
||||
(event.target as HTMLElement).scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative rounded-2xl py-4 flex flex-col">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h3 class="text-xl">
|
||||
{$t(project.nameSlug)}
|
||||
</h3>
|
||||
<hr class="grow border-slate-800">
|
||||
</div>
|
||||
|
||||
<p class="text-slate-400 mb-4 text-sm">
|
||||
{@html $t(project.descriptionSlug)}
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="project__gallery grid gap-1 grid-flow-col mb-4 mt-auto md:rounded"
|
||||
>
|
||||
{#each project.images as image}
|
||||
<img
|
||||
class="rounded"
|
||||
alt={`${$t(project.nameSlug)} screenshot`}
|
||||
src={image}
|
||||
on:click={visifyImage}
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
{#each project.links as link}
|
||||
<a
|
||||
target="_blank"
|
||||
href={link.href}
|
||||
class="flex gap-1 items-center py-1 text-sm"
|
||||
>
|
||||
{#if link.icon}
|
||||
<svelte:component this={link.icon} />
|
||||
{/if}
|
||||
{$t(link.textSlug)}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.project__gallery {
|
||||
overflow: auto;
|
||||
grid-auto-columns: calc(50% - 16px);
|
||||
margin-inline: -8px;
|
||||
padding-inline: 8px;
|
||||
|
||||
@media screen(md) {
|
||||
margin-inline: 0;
|
||||
padding-inline: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
19
src/routes/Projects/Projects.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import {t} from "$lib/translations";
|
||||
import Project from "./Project.svelte";
|
||||
import {projects} from "./projects";
|
||||
</script>
|
||||
|
||||
<section class="px-2 md:px-8 mb-16 max-w-[1280px] mx-auto">
|
||||
<h2 class="text-3xl mb-4 md:text-4xl">
|
||||
{$t('projects.h')}
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each projects as project}
|
||||
<Project
|
||||
project={project}
|
||||
></Project>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
156
src/routes/Projects/projects.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import {SvelteComponent} from "svelte";
|
||||
|
||||
import MaterialSymbolsGlobe from '~icons/material-symbols/globe';
|
||||
import MaterialSymbolsExtension from '~icons/material-symbols/extension';
|
||||
import MdiGithub from '~icons/mdi/github';
|
||||
|
||||
import PureCityImg1 from "$lib/assets/projects/pure_city/1.png";
|
||||
import PureCityImg2 from "$lib/assets/projects/pure_city/2.png";
|
||||
import VkMuteImg1 from "$lib/assets/projects/vk_mute/1.png";
|
||||
import TpkArtelImg1 from "$lib/assets/projects/tpk_artel/1.png";
|
||||
import TpkArtelImg2 from "$lib/assets/projects/tpk_artel/2.png";
|
||||
import TpkArtelImg3 from "$lib/assets/projects/tpk_artel/3.png";
|
||||
import GamesImg1 from "$lib/assets/projects/games/1.png";
|
||||
import GamesImg2 from "$lib/assets/projects/games/2.png";
|
||||
import GamesImg3 from "$lib/assets/projects/games/3.png";
|
||||
import MusanthropeImg1 from "$lib/assets/projects/musanthrope/1.png";
|
||||
import MusanthropeImg2 from "$lib/assets/projects/musanthrope/2.png";
|
||||
import SleepyCareImg1 from "$lib/assets/projects/sleepy_care/1.png";
|
||||
import SleepyCareImg2 from "$lib/assets/projects/sleepy_care/2.png";
|
||||
import SleepyCareImg3 from "$lib/assets/projects/sleepy_care/3.png";
|
||||
import VkAdultImg1 from "$lib/assets/projects/vk_adult/1.png";
|
||||
|
||||
export type TProject = {
|
||||
nameSlug: string
|
||||
descriptionSlug: string
|
||||
images: string[]
|
||||
links: {
|
||||
href: string
|
||||
textSlug: string
|
||||
icon?: SvelteComponent
|
||||
}[]
|
||||
}
|
||||
|
||||
export const projects = [
|
||||
{
|
||||
nameSlug: 'projects.pure_city.name',
|
||||
descriptionSlug: 'projects.pure_city.description',
|
||||
images: [
|
||||
PureCityImg1,
|
||||
PureCityImg2
|
||||
],
|
||||
links: [
|
||||
{
|
||||
href: 'https://pure-city.ru',
|
||||
textSlug: 'projects.link_types.website',
|
||||
icon: MaterialSymbolsGlobe
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nameSlug: 'projects.vk_mute.name',
|
||||
descriptionSlug: 'projects.vk_mute.description',
|
||||
images: [
|
||||
VkMuteImg1
|
||||
],
|
||||
links: [
|
||||
{
|
||||
href: 'https://chromewebstore.google.com/detail/vk-mute/mcnkfnjggkbenehgfelnnkklpkpjeibl',
|
||||
textSlug: 'projects.link_types.chrome',
|
||||
icon: MaterialSymbolsExtension
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/anatolykopyl/vk-mute',
|
||||
textSlug: 'projects.link_types.github',
|
||||
icon: MdiGithub
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nameSlug: 'projects.tpk_artel.name',
|
||||
descriptionSlug: 'projects.tpk_artel.description',
|
||||
images: [
|
||||
TpkArtelImg1,
|
||||
TpkArtelImg2,
|
||||
TpkArtelImg3
|
||||
],
|
||||
links: [
|
||||
{
|
||||
href: 'https://tpk-artel.ru',
|
||||
textSlug: 'projects.link_types.website',
|
||||
icon: MaterialSymbolsGlobe
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nameSlug: 'projects.games.name',
|
||||
descriptionSlug: 'projects.games.description',
|
||||
images: [
|
||||
GamesImg1,
|
||||
GamesImg2,
|
||||
GamesImg3
|
||||
],
|
||||
links: [
|
||||
{
|
||||
href: 'https://games.kopyl.dev',
|
||||
textSlug: 'projects.link_types.website',
|
||||
icon: MaterialSymbolsGlobe
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nameSlug: 'projects.musanthrope.name',
|
||||
descriptionSlug: 'projects.musanthrope.description',
|
||||
images: [
|
||||
MusanthropeImg1,
|
||||
MusanthropeImg2
|
||||
],
|
||||
links: [
|
||||
{
|
||||
href: 'https://musanthrope.kopyl.dev',
|
||||
textSlug: 'projects.link_types.website',
|
||||
icon: MaterialSymbolsGlobe
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nameSlug: 'projects.vk_adult.name',
|
||||
descriptionSlug: 'projects.vk_adult.description',
|
||||
images: [
|
||||
VkAdultImg1
|
||||
],
|
||||
links: [
|
||||
{
|
||||
href: 'https://chromewebstore.google.com/detail/vk-adult/laiidhgadnejimjkaobcecboppgbeppa',
|
||||
textSlug: 'projects.link_types.chrome',
|
||||
icon: MaterialSymbolsExtension
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/anatolykopyl/vk-adult',
|
||||
textSlug: 'projects.link_types.github',
|
||||
icon: MdiGithub
|
||||
},
|
||||
{
|
||||
href: 'https://vk-adult.kopyl.dev',
|
||||
textSlug: 'projects.link_types.website',
|
||||
icon: MaterialSymbolsGlobe
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nameSlug: 'projects.sleepy_care.name',
|
||||
descriptionSlug: 'projects.sleepy_care.description',
|
||||
images: [
|
||||
SleepyCareImg1,
|
||||
SleepyCareImg2,
|
||||
SleepyCareImg3
|
||||
],
|
||||
links: [
|
||||
{
|
||||
href: 'https://sleepy.care',
|
||||
textSlug: 'projects.link_types.website',
|
||||
icon: MaterialSymbolsGlobe
|
||||
}
|
||||
]
|
||||
}
|
||||
] as TProject[]
|
||||
BIN
static/favicon.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
12
svelte.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
preprocess: vitePreprocess(),
|
||||
kit: {
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
9
tailwind.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
19
tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
16
vite.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
import { enhancedImages } from '@sveltejs/enhanced-img';
|
||||
import Icons from 'unplugin-icons/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
enhancedImages(),
|
||||
sveltekit(),
|
||||
Icons({
|
||||
compiler: 'svelte',
|
||||
})
|
||||
],
|
||||
preprocess: vitePreprocess(),
|
||||
});
|
||||