first commit
This commit is contained in:
13
src/app.d.ts
vendored
Normal file
13
src/app.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
22
src/app.html
Normal file
22
src/app.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
|
||||
<title>Andrei Molnar</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>👨🏻💻</text></svg>">
|
||||
<meta name="description" content="Java, Python, I like building stuff.">
|
||||
<meta name="author" content="Andrei Molnar">
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/beercss@3.10.8/dist/cdn/beer.min.css" rel="stylesheet" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/beercss@3.10.8/dist/cdn/beer.min.js"></script>
|
||||
<link rel="stylesheet" type='text/css' href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css" />
|
||||
|
||||
<link rel="stylesheet" href="/app.css">
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
%sveltekit.body%
|
||||
</body>
|
||||
</html>
|
44
src/lib/components/CardList.svelte
Normal file
44
src/lib/components/CardList.svelte
Normal file
@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { inview } from 'svelte-inview'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { _ } from 'svelte-i18n'
|
||||
|
||||
const CHIPS = [
|
||||
['intl', 'language'],
|
||||
['cicd', 'autorenew'],
|
||||
['auto', 'automation'],
|
||||
['test', 'bug_report'],
|
||||
['bd', 'database'],
|
||||
['android', 'android'],
|
||||
['web', 'bookmark'],
|
||||
['systems', 'terminal'],
|
||||
['mvp', 'experiment'],
|
||||
['design', 'edit'],
|
||||
]
|
||||
|
||||
let chips = $state([])
|
||||
|
||||
function addChip() {
|
||||
const c = CHIPS.pop()
|
||||
if (!c) return;
|
||||
chips.push(c)
|
||||
setTimeout(addChip, 250)
|
||||
}
|
||||
|
||||
</script>
|
||||
<div style="min-height: 80vh;"
|
||||
use:inview
|
||||
oninview_enter={()=>setTimeout(addChip, 300)}
|
||||
>
|
||||
<h5 id="capabilities" class="center-align primary-text">{$_('cards.title')}</h5>
|
||||
<div class="grid large-margin large-space">
|
||||
{#each chips as [title, icon]}
|
||||
<article class="transparent s6 m4 l3 small-height" in:fade>
|
||||
<nav class="vertical center-align">
|
||||
<h6>{$_(`cards.${title}`)}</h6>
|
||||
<i class="extra primary-text">{icon}</i>
|
||||
</nav>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
21
src/lib/components/FlyAnimate.svelte
Normal file
21
src/lib/components/FlyAnimate.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { inview } from 'svelte-inview'
|
||||
import { fly } from 'svelte/transition'
|
||||
|
||||
let { height = 'large', flyFrom = 'left', children} = $props()
|
||||
let visible = $state(false)
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if visible}
|
||||
<div in:fly={{x:(flyFrom=='right'? 50: -50), duration: 700}}>
|
||||
{@render children()}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="{height}-height">
|
||||
<div use:inview oninview_enter={()=>visible=true} class="absolute middle"></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
23
src/lib/components/Footer.svelte
Normal file
23
src/lib/components/Footer.svelte
Normal file
@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import {_} from 'svelte-i18n'
|
||||
|
||||
</script>
|
||||
|
||||
<div class="round small-blur large-margin large-padding grid">
|
||||
<div class="m l m2 l3">
|
||||
</div>
|
||||
<div class="s6 m4 l3">
|
||||
<p>{$_('footer.contact.title')}:</p>
|
||||
<p><i class="small">person</i> Andrei Molnar</p>
|
||||
<p><i class="small">email</i> <a href="mailto:1995am@pm.me" class="link">1995am@pm.me</a></p>
|
||||
<p><i class="small devicon-linkedin-plain"></i> <a href="https://www.linkedin.com/in/molnar-andrei/" class="link">LinkedIn</a></p>
|
||||
</div>
|
||||
<div class="s6 m4 l3">
|
||||
<p>{$_('footer.profile.title')}:</p>
|
||||
<p>Backend</p>
|
||||
<p>Apps</p>
|
||||
<p>Interfaces</p>
|
||||
</div>
|
||||
<div class="m l s0 m2 l3">
|
||||
</div>
|
||||
</div>
|
29
src/lib/components/GalleryDisplay.svelte
Normal file
29
src/lib/components/GalleryDisplay.svelte
Normal file
@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { gallery } from '$lib/state.svelte'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div.scroll {
|
||||
max-height: 96vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<dialog
|
||||
class:active={gallery.active}
|
||||
class="small-blur max"
|
||||
onclick={()=>gallery.active=false}
|
||||
>
|
||||
<div class="scroll">
|
||||
<nav>
|
||||
<div class="max"></div>
|
||||
<img src={gallery.src} onclick={e=>e.stopPropagation()}>
|
||||
<div class="max"></div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
<button class="absolute top right transparent border circle margin"
|
||||
onclick={()=>gallery.active=false}>
|
||||
<i class="error-text">close</i>
|
||||
</button>
|
||||
</dialog>
|
34
src/lib/components/GalleryImage.svelte
Normal file
34
src/lib/components/GalleryImage.svelte
Normal file
@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { gallery } from '$lib/state.svelte';
|
||||
let {src, title, href=false} = $props()
|
||||
let hover = $state(false)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.custom-spacing {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<article class="transparent round"
|
||||
onmouseenter={()=>hover=true}
|
||||
onmouseleave={()=>hover=false}>
|
||||
<img {src} class="responsive small-elevate">
|
||||
<div class="page" class:active={hover}>
|
||||
<button class="absolute bottom right circle transparent border no-margin" onclick={()=>{gallery.src=src; gallery.active=true}}>
|
||||
<i class="primary-text">search</i>
|
||||
</button>
|
||||
</div>
|
||||
<article class="absolute bottom left background tiny-margin custom-spacing medium-elevate">
|
||||
{#if href}
|
||||
<a class="link underline" href={href} target="_blank" rel="noopener noreferrer">
|
||||
{title}
|
||||
</a>
|
||||
{:else}
|
||||
<span>{title}</span>
|
||||
{/if}
|
||||
</article>
|
||||
</article>
|
67
src/lib/components/HeaderBar.svelte
Normal file
67
src/lib/components/HeaderBar.svelte
Normal file
@ -0,0 +1,67 @@
|
||||
<script>
|
||||
import { browser } from '$app/environment'
|
||||
import { onMount } from 'svelte'
|
||||
import { locale, _ } from 'svelte-i18n'
|
||||
|
||||
const locales = { 'es': '🇪🇸', 'en': '🇺🇸', 'ro': '🇷🇴'}
|
||||
const navlinks = { 'capabilities': 'cards', 'projects': 'projects', 'tech': 'stack' }
|
||||
|
||||
let darkMode = $state(false)
|
||||
function toggleDarkMode() {
|
||||
if (!browser) return;
|
||||
const bodyClasses = document.getElementsByTagName('body')[0].classList
|
||||
darkMode = !darkMode
|
||||
bodyClasses.remove(darkMode? 'light': 'dark')
|
||||
bodyClasses.add(darkMode? 'dark': 'light')
|
||||
}
|
||||
onMount(()=>{
|
||||
darkMode = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) || document.getElementsByTagName('body')[0].classList.contains('dark')
|
||||
})
|
||||
</script>
|
||||
|
||||
<h6 class="l m"><a href="#title">Andrei Molnar</a></h6>
|
||||
<div class="max l m"></div>
|
||||
<div class="l m">
|
||||
{#each Object.entries(navlinks) as [id, intl]}
|
||||
<a href="#{id}" class="chip round">{$_(`${intl}.slug`)}</a>
|
||||
{/each}
|
||||
</div>
|
||||
<button class="s transparent circle" data-ui="#nav-menu">
|
||||
<i>menu</i>
|
||||
<menu class="top no-wrap" id="nav-menu">
|
||||
<li>
|
||||
<a href="#title">Andrei Molnar</a>
|
||||
</li>
|
||||
{#each Object.entries(navlinks) as [id, intl]}
|
||||
<li>
|
||||
<a href="#{id}">{$_(`${intl}.slug`)}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</menu>
|
||||
</button>
|
||||
|
||||
<div class="max"></div>
|
||||
|
||||
<button class="s transparent circle" data-ui="#lang-menu">
|
||||
<i>language</i>
|
||||
<menu class="top no-wrap" id="lang-menu">
|
||||
{#each Object.entries(locales) as [name, flag]}
|
||||
<li data-ui="#lang-menu" onclick={()=>$locale = name}>{flag}</li>
|
||||
{/each}
|
||||
</menu>
|
||||
</button>
|
||||
<div class="tabs l m">
|
||||
{#each Object.entries(locales) as [name, flag]}
|
||||
<a
|
||||
class:active={$locale == name}
|
||||
onclick={()=>$locale = name}>{flag}</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<label class="switch icon">
|
||||
<input type="checkbox" onchange={toggleDarkMode} checked={darkMode}>
|
||||
<span>
|
||||
<i>light_mode</i>
|
||||
<i>dark_mode</i>
|
||||
</span>
|
||||
</label>
|
27
src/lib/components/projects/ArbiopsDev.svelte
Normal file
27
src/lib/components/projects/ArbiopsDev.svelte
Normal file
@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import GalleryImage from '$lib/components/GalleryImage.svelte'
|
||||
import { _ } from 'svelte-i18n'
|
||||
</script>
|
||||
|
||||
<h6>{$_('projects.arbiopsWeb.title')}</h6>
|
||||
<div class="grid">
|
||||
<div class="s12 m6 l4">
|
||||
<GalleryImage
|
||||
src="/img/arbiops/login.png"
|
||||
title={$_('projects.arbiopsWeb.pic1')}/>
|
||||
</div>
|
||||
<div class="s12 m6 l4">
|
||||
<GalleryImage
|
||||
src="/img/arbiops/chains.png"
|
||||
title={$_('projects.arbiopsWeb.pic2')}/>
|
||||
</div>
|
||||
<div class="s12 m6 l4">
|
||||
<GalleryImage
|
||||
src="/img/arbiops/files.png"
|
||||
title={$_('projects.arbiopsWeb.pic3')}/>
|
||||
</div>
|
||||
</div>
|
||||
<blockquote>
|
||||
<p>{$_('projects.arbiopsWeb.paragraph1')}</p>
|
||||
<p>{$_('projects.arbiopsWeb.paragraph2')}</p>
|
||||
</blockquote>
|
22
src/lib/components/projects/ArbiopsProcess.svelte
Normal file
22
src/lib/components/projects/ArbiopsProcess.svelte
Normal file
@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import GalleryImage from '$lib/components/GalleryImage.svelte'
|
||||
import { _ } from 'svelte-i18n'
|
||||
</script>
|
||||
|
||||
<h6>{$_('projects.arbiopsProcesses.title')}</h6>
|
||||
<div class="grid">
|
||||
<div class="s12 m6 l4">
|
||||
<GalleryImage
|
||||
src="/img/arbiops/orchestration.png"
|
||||
title={$_('projects.arbiopsProcesses.pic1')}/>
|
||||
</div>
|
||||
<div class="s12 m6 l4">
|
||||
<GalleryImage
|
||||
src="/img/arbiops/devops.png"
|
||||
title={$_('projects.arbiopsProcesses.pic2')}/>
|
||||
</div>
|
||||
</div>
|
||||
<blockquote>
|
||||
<p>{$_('projects.arbiopsProcesses.paragraph1')}</p>
|
||||
<p>{$_('projects.arbiopsProcesses.paragraph2')}</p>
|
||||
</blockquote>
|
24
src/lib/components/projects/IqaTaxTools.svelte
Normal file
24
src/lib/components/projects/IqaTaxTools.svelte
Normal file
@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n'
|
||||
import GalleryImage from '$lib/components/GalleryImage.svelte'
|
||||
</script>
|
||||
|
||||
<h6>{$_('projects.iqaContab.title')}</h6>
|
||||
<div class="grid">
|
||||
<div class="s12 m6 l4">
|
||||
<GalleryImage
|
||||
src="/img/iqa/landing.png"
|
||||
title={$_('projects.iqaContab.pic1')}
|
||||
href="https://iqataxtools.com"/>
|
||||
</div>
|
||||
<div class="s12 m6 l4">
|
||||
<GalleryImage
|
||||
src="/img/iqa/simple.png"
|
||||
title={$_('projects.iqaContab.pic2')}/>
|
||||
</div>
|
||||
</div>
|
||||
<blockquote>
|
||||
<p>{$_('projects.iqaContab.paragraph1')}</p>
|
||||
<p>{$_('projects.iqaContab.paragraph2')}</p>
|
||||
<p>{$_('projects.iqaContab.paragraph3')}</p>
|
||||
</blockquote>
|
28
src/lib/components/projects/IqaWebPage.svelte
Normal file
28
src/lib/components/projects/IqaWebPage.svelte
Normal file
@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import GalleryImage from '$lib/components/GalleryImage.svelte'
|
||||
import { _ } from 'svelte-i18n'
|
||||
</script>
|
||||
|
||||
<h6>{$_('projects.iqaWeb.title')}</h6>
|
||||
<div class="grid">
|
||||
<div class="s12 m6 l4">
|
||||
<GalleryImage
|
||||
src="/img/iqa/corpo.png"
|
||||
title={$_('projects.iqaWeb.pic1')}
|
||||
href="https://iqa.iqataxtools.com"/>
|
||||
</div>
|
||||
<div class="s12 m6 l4">
|
||||
<GalleryImage
|
||||
src="/img/iqa/services.png"
|
||||
title={$_('projects.iqaWeb.pic2')}/>
|
||||
</div>
|
||||
<div class="s12 m6 l4">
|
||||
<GalleryImage
|
||||
src="/img/iqa/reactive.png"
|
||||
title={$_('projects.iqaWeb.pic3')}/>
|
||||
</div>
|
||||
</div>
|
||||
<blockquote>
|
||||
<p>{$_('projects.iqaWeb.paragraph1')}</p>
|
||||
<p>{$_('projects.iqaWeb.paragraph2')}</p>
|
||||
</blockquote>
|
37
src/lib/components/projects/Projects.svelte
Normal file
37
src/lib/components/projects/Projects.svelte
Normal file
@ -0,0 +1,37 @@
|
||||
<script>
|
||||
import FlyRight from '$lib/components/FlyAnimate.svelte'
|
||||
import ArbiopsDev from './ArbiopsDev.svelte'
|
||||
import ArbiopsProcess from './ArbiopsProcess.svelte'
|
||||
import IqaTaxTools from './IqaTaxTools.svelte'
|
||||
import IqaWebPage from './IqaWebPage.svelte'
|
||||
import YourProjectHere from './YourProjectHere.svelte'
|
||||
import { _ } from 'svelte-i18n'
|
||||
</script>
|
||||
|
||||
<h5 id="projects" class="center-align primary-text">{$_('projects.title')}</h5>
|
||||
<div class="small-space"></div>
|
||||
|
||||
<FlyRight>
|
||||
<ArbiopsDev/>
|
||||
</FlyRight>
|
||||
<div class="large-space"></div>
|
||||
|
||||
<FlyRight>
|
||||
<ArbiopsProcess/>
|
||||
</FlyRight>
|
||||
<div class="large-space"></div>
|
||||
|
||||
<FlyRight>
|
||||
<IqaTaxTools/>
|
||||
</FlyRight>
|
||||
<div class="large-space"></div>
|
||||
|
||||
<FlyRight>
|
||||
<IqaWebPage/>
|
||||
</FlyRight>
|
||||
<div class="large-space"></div>
|
||||
|
||||
<FlyRight>
|
||||
<YourProjectHere height='large'/>
|
||||
</FlyRight>
|
||||
<div class="large-space"></div>
|
14
src/lib/components/projects/YourProjectHere.svelte
Normal file
14
src/lib/components/projects/YourProjectHere.svelte
Normal file
@ -0,0 +1,14 @@
|
||||
<script>
|
||||
import GalleryImage from '$lib/components/GalleryImage.svelte'
|
||||
import { _ } from 'svelte-i18n'
|
||||
</script>
|
||||
|
||||
<div class="center-align middle-align">
|
||||
<div>
|
||||
<h6>{$_('projects.yourProduct.title')}</h6>
|
||||
<div class="medium-width">
|
||||
<GalleryImage src="/img/futurism.jpg" title={$_('projects.yourProduct.pic1')}/>
|
||||
</div>
|
||||
<p><a class="button" href="mailto:1995am@pm.me">{$_('projects.yourProduct.button')}</a></p>
|
||||
</div>
|
||||
</div>
|
42
src/lib/components/stack/Stack.svelte
Normal file
42
src/lib/components/stack/Stack.svelte
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
<script>
|
||||
import FlyAnimate from '$lib/components/FlyAnimate.svelte'
|
||||
import TechItem from './TechItem.svelte'
|
||||
import {_} from 'svelte-i18n'
|
||||
</script>
|
||||
|
||||
<h5 id="tech" class="center-align primary-text">{$_('stack.title')}</h5>
|
||||
<div class="small-space"></div>
|
||||
<FlyAnimate>
|
||||
<div class="grid">
|
||||
<TechItem title='Python' icon='python' highlight=true/>
|
||||
<TechItem title='Java' icon='java'/>
|
||||
<TechItem title='Kotlin' icon='kotlin'/>
|
||||
<div class="s12"></div>
|
||||
<TechItem title='FastAPI' icon='fastapi' highlight=true/>
|
||||
<TechItem title='Java Spring' icon='spring'/>
|
||||
<TechItem title='Javalin' icon='java'/>
|
||||
<div class="s12"></div>
|
||||
<TechItem title='Svelte' icon='svelte' highlight=true/>
|
||||
<TechItem title='React' icon='react'/>
|
||||
<TechItem title='NodeJS' icon='nodejs'/>
|
||||
<TechItem title='BeerCSS' icon='css3'/>
|
||||
<div class="s12"></div>
|
||||
<TechItem title='Docker' icon='docker'/>
|
||||
<TechItem title='NGINX' icon='nginx'/>
|
||||
<TechItem title='AWS' icon='amazonwebservices'/>
|
||||
<TechItem title='Linux' icon='linux'/>
|
||||
<div class="s12"></div>
|
||||
<TechItem title='MongoDB' icon='mongodb'/>
|
||||
<TechItem title='PostgreSQL' icon='postgresql'/>
|
||||
<TechItem title='Redis' icon='redis'/>
|
||||
<TechItem title='SQLite' icon='sqlite'/>
|
||||
<div class="s12"></div>
|
||||
<TechItem title='Playwright' icon='playwright'/>
|
||||
<TechItem title='Junit' icon='junit'/>
|
||||
<div class="s12"></div>
|
||||
<TechItem title='Git' icon='git'/>
|
||||
<TechItem title='Github Actions' icon='githubactions'/>
|
||||
</div>
|
||||
|
||||
</FlyAnimate>
|
8
src/lib/components/stack/TechItem.svelte
Normal file
8
src/lib/components/stack/TechItem.svelte
Normal file
@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
let {title, icon, highlight=false} = $props()
|
||||
</script>
|
||||
|
||||
<h6 class="l3 m4 s6" class:primary-text={highlight}>
|
||||
<i class="devicon-{icon}-plain"></i>
|
||||
<span>{title}</span>
|
||||
</h6>
|
182
src/lib/components/title/FallingIcons.svelte
Normal file
182
src/lib/components/title/FallingIcons.svelte
Normal file
@ -0,0 +1,182 @@
|
||||
<script lang="ts">
|
||||
import LogoIcon from './LogoIcon.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
let container: HTMLElement | null = $state(null)
|
||||
|
||||
let POSSIBLE_ICONS = $state([
|
||||
'java', 'python', 'kotlin',
|
||||
'spring', 'fastapi',
|
||||
'android', 'chrome',
|
||||
'svelte', 'react', 'html5', 'css3', 'javascript',
|
||||
'nodejs',
|
||||
'docker', 'nginx', 'amazonwebservices',
|
||||
'mongodb', 'sqlite', 'postgresql', 'redis',
|
||||
'playwright', 'junit',
|
||||
'git', 'githubactions',
|
||||
'archlinux', 'ubuntu', 'debian', 'fedora', 'linux', 'windows11',
|
||||
'raspberrypi',
|
||||
])
|
||||
|
||||
class Phys {
|
||||
x: Number
|
||||
y: Number
|
||||
xspeed: Number
|
||||
yspeed: Number
|
||||
icon: String
|
||||
constructor(x, y, xspeed, yspeed, icon) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
this.xspeed = xspeed
|
||||
this.yspeed = yspeed
|
||||
this.icon = icon
|
||||
}
|
||||
}
|
||||
|
||||
let items: Array<Phys> = $state([])
|
||||
let scroll = 0;
|
||||
let lastScroll = 0;
|
||||
|
||||
let MAX_Y = 0;
|
||||
let MAX_X = 0;
|
||||
const DIAMETER = 48;
|
||||
const GRAVITY = 0.03;
|
||||
const FRICTION = 0.005;
|
||||
const TERMINAL_VELOCITY = 5;
|
||||
const FRAMERATE = 1/30;
|
||||
|
||||
$effect(()=>{
|
||||
if (container != null) {
|
||||
const rect = container.getBoundingClientRect()
|
||||
MAX_Y = rect.height - DIAMETER
|
||||
MAX_X = rect.width - DIAMETER
|
||||
spawnTimed()
|
||||
container = null
|
||||
}
|
||||
})
|
||||
|
||||
onMount(()=>{
|
||||
const interval = setInterval(() => {
|
||||
physics()
|
||||
}, FRAMERATE);
|
||||
const onscroll = ()=>{scroll = window.scrollY}
|
||||
window.addEventListener('scroll', onscroll)
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
window.removeEventListener('scroll', onscroll)
|
||||
};
|
||||
})
|
||||
|
||||
function spawnTimed() {
|
||||
const icon = POSSIBLE_ICONS.pop()
|
||||
if (!icon) return;
|
||||
|
||||
items.push({x: Math.random() * 200 + MAX_X/2 - 100, y: -DIAMETER, xspeed: 0.0, yspeed: 0.0, icon: icon})
|
||||
setTimeout(spawnTimed, Math.random()*300)
|
||||
}
|
||||
|
||||
function respawn(item) {
|
||||
items.splice(items.findIndex(i=>i.icon == item.icon), 1)
|
||||
POSSIBLE_ICONS.push(item.icon)
|
||||
spawnTimed()
|
||||
}
|
||||
|
||||
function physics() {
|
||||
for (const i of items) {
|
||||
|
||||
// on floor, or gravity
|
||||
const grounded = (i.y + i.yspeed) > MAX_Y
|
||||
if (grounded) {
|
||||
i.y = MAX_Y
|
||||
if (i.yspeed > 0) i.yspeed = - Math.abs(i.yspeed / 2)
|
||||
} else {
|
||||
i.yspeed += GRAVITY
|
||||
}
|
||||
|
||||
if (lastScroll != scroll) {
|
||||
i.yspeed -= (scroll - lastScroll) / 100
|
||||
}
|
||||
|
||||
// horizontal friction
|
||||
// TODO: improve sudden stopping
|
||||
const friction = grounded? FRICTION *5 : FRICTION
|
||||
if (i.xspeed > -friction && i.xspeed < friction) {
|
||||
i.xspeed = 0
|
||||
} else if (i.xspeed >= friction) {
|
||||
i.xspeed -= friction
|
||||
} else if (i.xspeed <= -friction) {
|
||||
i.xspeed += friction
|
||||
}
|
||||
|
||||
// hit walls
|
||||
if (i.x + i.xspeed > MAX_X) {
|
||||
i.x = MAX_X
|
||||
i.xspeed = -Math.abs(i.xspeed/2)
|
||||
} else if (i.x - i.xspeed < 0) {
|
||||
i.x = 0
|
||||
i.xspeed = Math.abs(i.xspeed/2)
|
||||
}
|
||||
|
||||
// clamp speed to terminal velocity
|
||||
i.xspeed = Math.max(-TERMINAL_VELOCITY, Math.min(TERMINAL_VELOCITY, i.xspeed))
|
||||
i.yspeed = Math.max(-TERMINAL_VELOCITY, Math.min(TERMINAL_VELOCITY, i.yspeed))
|
||||
|
||||
// be moved by mouse
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const a = items[i];
|
||||
|
||||
for (let j = i + 1; j < items.length; j++) {
|
||||
const b = items[j];
|
||||
|
||||
const dx = b.x - a.x;
|
||||
const dy = b.y - a.y;
|
||||
const dist = Math.hypot(dx, dy);
|
||||
|
||||
if (dist < DIAMETER) {
|
||||
const overlap = DIAMETER - dist;
|
||||
|
||||
// normalize collision vector
|
||||
const nx = dx / dist;
|
||||
const ny = dy / dist;
|
||||
|
||||
// avoid overlap
|
||||
const correction = overlap / 2;
|
||||
a.x -= nx * correction;
|
||||
a.y -= ny * correction;
|
||||
b.x += nx * correction;
|
||||
b.y += ny * correction;
|
||||
|
||||
// calc speed
|
||||
const dvx = a.xspeed - b.xspeed;
|
||||
const dvy = a.yspeed - b.yspeed;
|
||||
const dot = dvx * nx + dvy * ny;
|
||||
|
||||
a.xspeed -= dot * nx;
|
||||
a.yspeed -= dot * ny;
|
||||
b.xspeed += dot * nx;
|
||||
b.yspeed += dot * ny;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const i of items) {
|
||||
// move
|
||||
i.y += i.yspeed
|
||||
i.x += i.xspeed
|
||||
}
|
||||
lastScroll = scroll
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={container}
|
||||
style="width: 100%; height: 100%"
|
||||
class="absolute middle center">
|
||||
|
||||
{#each items as item (item.icon)}
|
||||
<LogoIcon icon={item.icon} y={item.y} x={item.x} onclick={()=>respawn(item)}/>
|
||||
{/each}
|
||||
</div>
|
15
src/lib/components/title/LogoIcon.svelte
Normal file
15
src/lib/components/title/LogoIcon.svelte
Normal file
@ -0,0 +1,15 @@
|
||||
<script>
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
let {icon, x=0, y=0, onclick=null} = $props()
|
||||
|
||||
</script>
|
||||
|
||||
<div class="circle surface-container-highest small-elevate small-padding"
|
||||
style="position: absolute;"
|
||||
style:top={`${y}px`}
|
||||
style:left={`${x}px`}
|
||||
in:fade
|
||||
onclick={onclick}>
|
||||
<i class={`devicon-${icon}-plain extra`}></i>
|
||||
</div>
|
19
src/lib/components/title/Title.svelte
Normal file
19
src/lib/components/title/Title.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import {_} from 'svelte-i18n'
|
||||
import FallingIcons from './FallingIcons.svelte'
|
||||
</script>
|
||||
<style>
|
||||
#title {
|
||||
height: 90vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="title" class="no-margin">
|
||||
<FallingIcons/>
|
||||
<div class="center-align middle">
|
||||
<div class="page active">
|
||||
<h4 class="primary-text">👨🏻💻 {$_('title.title')}</h4>
|
||||
<h5>{$_('title.subtitle')}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
72
src/lib/intl/en.js
Normal file
72
src/lib/intl/en.js
Normal file
@ -0,0 +1,72 @@
|
||||
export const en = {
|
||||
title: {
|
||||
title: "I bring your projects into reality",
|
||||
subtitle: "From prototype to product — all in one place"
|
||||
},
|
||||
cards: {
|
||||
slug: "Capabilities",
|
||||
title: "What I can do for you",
|
||||
design: "Design",
|
||||
mvp: "Prototypes / MVP",
|
||||
systems: "Systems",
|
||||
web: "Web Apps",
|
||||
android: "Android Apps",
|
||||
bd: "Databases",
|
||||
test: "Testing",
|
||||
auto: "Automation",
|
||||
cicd: "CI/CD",
|
||||
intl: "Internationalization"
|
||||
},
|
||||
projects: {
|
||||
slug: "Portfolio",
|
||||
title: "Projects I'm proud of",
|
||||
arbiopsWeb: {
|
||||
title: "Arbiops: internal admin tool",
|
||||
pic1: "Credentials",
|
||||
pic2: "Process control",
|
||||
pic3: "Private cloud",
|
||||
paragraph1: "Database control, process monitoring, and integration with tools to improve collaboration: git repository (gitea), push notifications (NTFY), chat (rocketchat), etc.",
|
||||
paragraph2: "Arbiops S.L. needed a tool to monitor and control its economic activity. After analyzing the requirements, I developed a Web App with Svelte. It interfaces with the database and performs functions such as backups and admin tasks."
|
||||
},
|
||||
arbiopsProcesses: {
|
||||
title: "Arbiops: Process Orchestrator",
|
||||
pic1: "Process master",
|
||||
pic2: "DevOps",
|
||||
paragraph1: "Automatic orchestration of processes with simple and easy-to-maintain Python scripts, continuous DevOps with GitHub Actions.",
|
||||
paragraph2: "Due to Arbiops' complex architecture and advanced requirements regarding external software (blockchain nodes), we decided to develop an elegant solution to orchestrate and maintain all processes."
|
||||
},
|
||||
iqaContab: {
|
||||
title: "IqaTaxTools: tool to ease accounting operations",
|
||||
pic1: "Main page",
|
||||
pic2: "Simple interface",
|
||||
paragraph1: "Accounting tool for issuing tax invoices in Romania, with integrated payments via Stripe.",
|
||||
paragraph2: "Intelligence Quality Assurance had a prototype that allowed scanning certain PDFs for invoicing. After working with them as a contractor, we decided to collaborate on expanding the prototype into a product. This involved reverse engineering and PDF data extraction.",
|
||||
paragraph3: "I also developed their corporate website, a demo video for the app, and contributed to ensuring the product was successfully launched.",
|
||||
},
|
||||
iqaWeb: {
|
||||
title: "IQA: corporate website",
|
||||
pic1: "Company website",
|
||||
pic2: "Dark mode",
|
||||
pic3: "Responsive",
|
||||
paragraph1: "Design and implementation of a corporate website with email support. The goal was to improve their image as a consultancy — a website is a company’s business card.",
|
||||
paragraph2: "Includes dark mode, responsive layout (mobile-friendly), and SEO.",
|
||||
},
|
||||
yourProduct: {
|
||||
title: "Have a product in mind?",
|
||||
pic1: "Our next project",
|
||||
button: "Send me an email and we can talk",
|
||||
}
|
||||
},
|
||||
stack: {
|
||||
slug: "Technologies",
|
||||
title: "My Toolbox",
|
||||
},
|
||||
footer: {
|
||||
contact: {
|
||||
title: "Contact",
|
||||
},
|
||||
profile: {
|
||||
title: "Profile",
|
||||
},
|
||||
},
|
||||
};
|
72
src/lib/intl/es.js
Normal file
72
src/lib/intl/es.js
Normal file
@ -0,0 +1,72 @@
|
||||
export const es = {
|
||||
title: {
|
||||
title: "Hago realidad tus proyectos",
|
||||
subtitle: "De prototipo a producto — todo en un solo lugar"
|
||||
},
|
||||
cards: {
|
||||
slug: "Capacidades",
|
||||
title: "Lo que puedo hacer por ti:",
|
||||
design: "Diseño",
|
||||
mvp: "Prototipos / MVP",
|
||||
systems: "Sistemas",
|
||||
web: "Web Apps",
|
||||
android: "Apps Android",
|
||||
bd: "Bases de Datos",
|
||||
test: "Testing",
|
||||
auto: "Automatizacion",
|
||||
cicd: "Integración Contínua",
|
||||
intl: "Internalización"
|
||||
},
|
||||
projects: {
|
||||
slug: "Portfolio",
|
||||
title: "Algunos proyectos en los que he trabajado",
|
||||
arbiopsWeb: {
|
||||
title: "Arbiops: herramienta interna de administracion",
|
||||
pic1: "Credenciales",
|
||||
pic2: "Control de procesos",
|
||||
pic3: "Cloud privada",
|
||||
paragraph1: "Control de base de datos, monitorizacion de procesos, e integracion con herramientas varias para mejorar la colaboracion: repositorio git (gitea), notificaciones push (NTFY), chat (rocketchat), etc.",
|
||||
paragraph2: "Arbiops S.L. Necesito una herramienta para monitorizar y controlar su actividad economica. Despues de analizar los requisitos, desarrolle una Web App con Svelte. Esta hace de interfaz con su BBDD y realiza diferentes funciones como backups, y administracion."
|
||||
},
|
||||
arbiopsProcesses: {
|
||||
title: "Arbiops: Orquestrador de Procesos",
|
||||
pic1: "Maestro de procesos",
|
||||
pic2: "DevOps",
|
||||
paragraph1: "Orquestrado de procesos automatico con scripts simples y faciles de mantener de python, devops continuo mediante github actions.",
|
||||
paragraph2: "Debido a la compleja arquitectura y requisitos avanzados de Arbiops respecto a programas externos (nodos de blockchain), hemos decidido desarrolla una solucion elegante para orquestrar y mantener todos los procesos."
|
||||
},
|
||||
iqaContab: {
|
||||
title: "IqaTaxTools: app para aligerar operaciones de contabilidad",
|
||||
pic1: "Pagina principal",
|
||||
pic2: "Operativa simple",
|
||||
paragraph1: "Herramienta de contabilidad para emision de factura sobre impuestos en Rumania, con pagos integrados mediante Stripe.",
|
||||
paragraph2: "Intelligence Quality Assurance poseia un prototipo que permite el escaneado de ciertos ficheros pdf para facturacion. Despues de trabajar con ellos como contratista, decidimos colaborar en la expansion del prototipo a producto. Este proceso incluyo ingenieria inversa e investigacion sobre los ficheros pdf y su formato, tanto como la extraccion de sus datos.",
|
||||
paragraph3: "Tambien desarrolle su pagina web corporativa y un video de demo para su app, y realice otras actividades para asegurar que su producto saliese en adelante."
|
||||
},
|
||||
iqaWeb: {
|
||||
title: "IQA: web corporativa",
|
||||
pic1: "Web de empresa",
|
||||
pic2: "Modo Oscuro",
|
||||
pic3: "Reactivo",
|
||||
paragraph1: "Diseño e implementacion de pagina web corporativa para la empresa, con email. Su objetivo es la mejora de su imagen como consultor: una web es la tarjeta de preentacion de una empresa de consultoria.",
|
||||
paragraph2: "Incluye modo oscuro, layout responsive (para mobiles), y SEO."
|
||||
},
|
||||
yourProduct: {
|
||||
title: "Tienes un producto en mente?",
|
||||
pic1: "Nuestro siguiente proyecto",
|
||||
button: "Mandame un correo y lo hablamos"
|
||||
}
|
||||
},
|
||||
stack: {
|
||||
slug: "Tecnologias",
|
||||
title: "Mi caja de Herramientas"
|
||||
},
|
||||
footer: {
|
||||
contact: {
|
||||
title: "Contacto",
|
||||
},
|
||||
profile: {
|
||||
title: "Perfil",
|
||||
},
|
||||
},
|
||||
};
|
72
src/lib/intl/ro.js
Normal file
72
src/lib/intl/ro.js
Normal file
@ -0,0 +1,72 @@
|
||||
export const ro = {
|
||||
title: {
|
||||
title: "Îți transform proiectele în realitate",
|
||||
subtitle: "De la prototip la produs — totul într-un singur loc"
|
||||
},
|
||||
cards: {
|
||||
slug: "Capabilități",
|
||||
title: "Ce pot face pentru dumneavoastră",
|
||||
design: "Design",
|
||||
mvp: "Prototipuri / MVP",
|
||||
systems: "Sisteme",
|
||||
web: "Aplicații Web",
|
||||
android: "Aplicații Android",
|
||||
bd: "Baze de date",
|
||||
test: "Testare",
|
||||
auto: "Automatizare",
|
||||
cicd: "CI/CD",
|
||||
intl: "Internaționalizare"
|
||||
},
|
||||
projects: {
|
||||
slug: "Portofoliu",
|
||||
title: "Proiecte de care sunt mândru",
|
||||
arbiopsWeb: {
|
||||
title: "Arbiops: unealtă internă de administrare",
|
||||
pic1: "Credențiale",
|
||||
pic2: "Controlul proceselor",
|
||||
pic3: "Cloud privat",
|
||||
paragraph1: "Controlul bazei de date, monitorizarea proceselor și integrarea cu diverse unelte pentru colaborare: depozit git (gitea), notificări push (NTFY), chat (rocketchat), etc.",
|
||||
paragraph2: "Arbiops S.L. avea nevoie de o unealtă pentru a monitoriza și controla activitatea economică. După analizarea cerințelor, am dezvoltat o aplicație web cu Svelte. Aceasta interacționează cu baza de date și oferă funcții precum backupuri și administrare."
|
||||
},
|
||||
arbiopsProcesses: {
|
||||
title: "Arbiops: Orchestrator de Procese",
|
||||
pic1: "Maestru de procese",
|
||||
pic2: "DevOps",
|
||||
paragraph1: "Orchestrare automată a proceselor cu scripturi Python simple și ușor de întreținut, DevOps continuu cu GitHub Actions.",
|
||||
paragraph2: "Datorită arhitecturii complexe a Arbiops și cerințelor avansate privind programe externe (noduri blockchain), am decis să dezvoltăm o soluție elegantă pentru a orchestra și menține toate procesele."
|
||||
},
|
||||
iqaContab: {
|
||||
title: "IqaTaxTools: unealtă pentru simplificarea contabilității",
|
||||
pic1: "Pagina principală",
|
||||
pic2: "Interfață simplă",
|
||||
paragraph1: "Unealtă de contabilitate pentru emiterea facturilor fiscale în România, cu plăți integrate prin Stripe.",
|
||||
paragraph2: "Intelligence Quality Assurance avea un prototip care permitea scanarea anumitor fișiere PDF pentru facturare. După colaborarea ca și contractor, am extins prototipul într-un produs final. Procesul a inclus inginerie inversă și extragerea datelor din PDF.",
|
||||
paragraph3: "Am dezvoltat și site-ul lor corporativ, un video demonstrativ al aplicației și am contribuit la lansarea cu succes a produsului."
|
||||
},
|
||||
iqaWeb: {
|
||||
title: "IQA: site corporativ",
|
||||
pic1: "Website companie",
|
||||
pic2: "Mod întunecat",
|
||||
pic3: "Responsiv",
|
||||
paragraph1: "Design și implementare a unui site corporativ cu email. Scopul a fost îmbunătățirea imaginii ca firmă de consultanță — un site web este cartea de vizită a unei firme.",
|
||||
paragraph2: "Include mod întunecat, layout adaptiv (pentru mobile) și SEO."
|
||||
},
|
||||
yourProduct: {
|
||||
title: "Ai un produs în minte?",
|
||||
pic1: "Următorul nostru proiect",
|
||||
button: "Trimite-mi un email și discutăm"
|
||||
}
|
||||
},
|
||||
stack: {
|
||||
slug: "Tehnologii",
|
||||
title: "Cutia mea de instrumente"
|
||||
},
|
||||
footer: {
|
||||
contact: {
|
||||
title: "Contact",
|
||||
},
|
||||
profile: {
|
||||
title: "Profil",
|
||||
},
|
||||
},
|
||||
};
|
4
src/lib/state.svelte.ts
Normal file
4
src/lib/state.svelte.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const gallery = $state({
|
||||
src: null,
|
||||
active: false,
|
||||
})
|
18
src/routes/+layout.svelte
Normal file
18
src/routes/+layout.svelte
Normal file
@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
export const ssr = false
|
||||
export const csr = true
|
||||
export const prerender = false
|
||||
|
||||
import {getLocaleFromNavigator, addMessages, init} from 'svelte-i18n'
|
||||
import { es } from '$lib/intl/es.js'
|
||||
import { ro } from '$lib/intl/ro.js'
|
||||
import { en } from '$lib/intl/en.js'
|
||||
addMessages("es", es)
|
||||
addMessages("en", en)
|
||||
addMessages("ro", ro)
|
||||
init({ fallbackLocale: "es", initialLocale: getLocaleFromNavigator() })
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<slot></slot>
|
34
src/routes/+page.svelte
Normal file
34
src/routes/+page.svelte
Normal file
@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import HeaderBar from '$lib/components/HeaderBar.svelte'
|
||||
import CardList from '$lib/components/CardList.svelte'
|
||||
import GalleryDisplay from '$lib/components/GalleryDisplay.svelte'
|
||||
|
||||
import Title from '$lib/components/title/Title.svelte'
|
||||
import Projects from '$lib/components/projects/Projects.svelte'
|
||||
import Stack from '$lib/components/stack/Stack.svelte'
|
||||
import Footer from '$lib/components/Footer.svelte'
|
||||
</script>
|
||||
|
||||
|
||||
<main class="responsive">
|
||||
<Title/>
|
||||
<hr>
|
||||
|
||||
<div class="large-space"></div>
|
||||
<CardList/>
|
||||
<div class="small-space"></div>
|
||||
<Projects/>
|
||||
<div class="small-space"></div>
|
||||
<Stack/>
|
||||
<div class="large-space"></div>
|
||||
<Footer/>
|
||||
</main>
|
||||
|
||||
<nav class="bottom s small-blur large-elevate">
|
||||
<HeaderBar/>
|
||||
</nav>
|
||||
|
||||
<nav class="top l m small-blur small-elevate">
|
||||
<HeaderBar/>
|
||||
</nav>
|
||||
<GalleryDisplay/>
|
Reference in New Issue
Block a user