Initial commit
This commit is contained in:
23
src/routes/+layout.svelte
Normal file
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
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
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
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
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
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
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
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
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[]
|
||||
Reference in New Issue
Block a user