first commit
19
README.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# My personal webpage
|
||||||
|
|
||||||
|
This is the source code for [my personal webpage](https://molnarandrei.com)
|
||||||
|
|
||||||
|
- Made with SvelteKit.
|
||||||
|
- Built with gitea actios (99% same as github actions).
|
||||||
|
- BeerCSS as the ui toolkit.
|
||||||
|
- Internationalized.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
- [ ] Automated tests with playwright, triggered by github actions
|
||||||
|
- [ ] Automated builds
|
||||||
|
- [ ] Books section:
|
||||||
|
- The pragmatic Programmer (haven't finished)
|
||||||
|
- Domain Driver Design (midway)
|
||||||
|
- Design Patterns: Elements of Reusable Object Oriented Software (done)
|
||||||
|
- Building Microservices (not started yet)
|
||||||
|
- [ ] More portfolio public code
|
||||||
|
- [ ] Store built with htmx and Java Spring because I want to learn html and work with templates.
|
2400
package-lock.json
generated
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "molnarandrei.com",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
|
"@sveltejs/adapter-auto": "^4.0.0",
|
||||||
|
"@sveltejs/adapter-static": "^3.0.8",
|
||||||
|
"@sveltejs/kit": "^2.16.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
|
"beercss": "^3.10.7",
|
||||||
|
"devicon": "^2.16.0",
|
||||||
|
"svelte": "^5.0.0",
|
||||||
|
"svelte-check": "^4.0.0",
|
||||||
|
"svelte-i18n": "^4.0.1",
|
||||||
|
"svelte-inview": "^4.0.4",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
"vite": "^6.2.5"
|
||||||
|
}
|
||||||
|
}
|
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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1,4 @@
|
|||||||
|
export const gallery = $state({
|
||||||
|
src: null,
|
||||||
|
active: false,
|
||||||
|
})
|
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
@ -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/>
|
91
static/app.css
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
:root body.dark {
|
||||||
|
--primary:#9ed75b;
|
||||||
|
--on-primary:#1e3700;
|
||||||
|
--primary-container:#2e4f00;
|
||||||
|
--on-primary-container:#b9f474;
|
||||||
|
--secondary:#bfcbad;
|
||||||
|
--on-secondary:#2a331e;
|
||||||
|
--secondary-container:#404a33;
|
||||||
|
--on-secondary-container:#dbe7c8;
|
||||||
|
--tertiary:#a0d0cc;
|
||||||
|
--on-tertiary:#003735;
|
||||||
|
--tertiary-container:#1f4e4b;
|
||||||
|
--on-tertiary-container:#bbece8;
|
||||||
|
--error:#ffb4ab;
|
||||||
|
--on-error:#690005;
|
||||||
|
--error-container:#93000a;
|
||||||
|
--on-error-container:#ffb4ab;
|
||||||
|
--background:#1b1c18;
|
||||||
|
--on-background:#e3e3db;
|
||||||
|
--surface:#121410;
|
||||||
|
--on-surface:#e3e3db;
|
||||||
|
--surface-variant:#44483d;
|
||||||
|
--on-surface-variant:#c5c8ba;
|
||||||
|
--outline:#8e9285;
|
||||||
|
--outline-variant:#44483d;
|
||||||
|
--shadow:#000000;
|
||||||
|
--scrim:#000000;
|
||||||
|
--inverse-surface:#e3e3db;
|
||||||
|
--inverse-on-surface:#30312c;
|
||||||
|
--inverse-primary:#3e6a00;
|
||||||
|
--surface-dim:#121410;
|
||||||
|
--surface-bright:#383a35;
|
||||||
|
--surface-container-lowest:#0d0f0b;
|
||||||
|
--surface-container-low:#1b1c18;
|
||||||
|
--surface-container:#1f201c;
|
||||||
|
--surface-container-high:#292a26;
|
||||||
|
--surface-container-highest:#343530;
|
||||||
|
|
||||||
|
background-color: var(--background);
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%239ed75b' fill-opacity='0.1'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
:root body.light {
|
||||||
|
--primary:#3e6a00;
|
||||||
|
--on-primary:#ffffff;
|
||||||
|
--primary-container:#b9f474;
|
||||||
|
--on-primary-container:#0f2000;
|
||||||
|
--secondary:#576249;
|
||||||
|
--on-secondary:#ffffff;
|
||||||
|
--secondary-container:#dbe7c8;
|
||||||
|
--on-secondary-container:#151e0b;
|
||||||
|
--tertiary:#386663;
|
||||||
|
--on-tertiary:#ffffff;
|
||||||
|
--tertiary-container:#bbece8;
|
||||||
|
--on-tertiary-container:#00201e;
|
||||||
|
--error:#ba1a1a;
|
||||||
|
--on-error:#ffffff;
|
||||||
|
--error-container:#ffdad6;
|
||||||
|
--on-error-container:#410002;
|
||||||
|
--background:#fdfcf5;
|
||||||
|
--on-background:#1b1c18;
|
||||||
|
--surface:#fafaf2;
|
||||||
|
--on-surface:#1b1c18;
|
||||||
|
--surface-variant:#e1e4d5;
|
||||||
|
--on-surface-variant:#44483d;
|
||||||
|
--outline:#75796c;
|
||||||
|
--outline-variant:#c5c8ba;
|
||||||
|
--shadow:#000000;
|
||||||
|
--scrim:#000000;
|
||||||
|
--inverse-surface:#30312c;
|
||||||
|
--inverse-on-surface:#f2f1e9;
|
||||||
|
--inverse-primary:#9ed75b;
|
||||||
|
--surface-dim:#dbdad3;
|
||||||
|
--surface-bright:#fafaf2;
|
||||||
|
--surface-container-lowest:#ffffff;
|
||||||
|
--surface-container-low:#f5f4ec;
|
||||||
|
--surface-container:#efeee7;
|
||||||
|
--surface-container-high:#e9e8e1;
|
||||||
|
--surface-container-highest:#e3e3db;
|
||||||
|
|
||||||
|
background-color: var(--background);
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%233e6a00' fill-opacity='0.1'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
scroll-margin-top: 5rem;
|
||||||
|
}
|
BIN
static/img/arbiops/chains.png
Normal file
After Width: | Height: | Size: 298 KiB |
BIN
static/img/arbiops/devops.png
Normal file
After Width: | Height: | Size: 143 KiB |
BIN
static/img/arbiops/files.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
static/img/arbiops/login.png
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
static/img/arbiops/orchestration.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
static/img/futurism.jpg
Normal file
After Width: | Height: | Size: 336 KiB |
BIN
static/img/iqa/corpo.png
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
static/img/iqa/landing.png
Normal file
After Width: | Height: | Size: 265 KiB |
BIN
static/img/iqa/reactive.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
static/img/iqa/services.png
Normal file
After Width: | Height: | Size: 346 KiB |
BIN
static/img/iqa/simple.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
static/img/logos/androidstudio.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
static/img/logos/apache.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
static/img/logos/aws.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
static/img/logos/beercss.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
static/img/logos/docker.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
static/img/logos/eth.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
static/img/logos/fastapi.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
static/img/logos/github_actions.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
static/img/logos/java.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
static/img/logos/javalin.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
static/img/logos/kotlin.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
static/img/logos/mongodb.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
static/img/logos/nginx.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
static/img/logos/node.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
static/img/logos/playwright.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
static/img/logos/postgre.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
static/img/logos/python.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
static/img/logos/react.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
static/img/logos/spring.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
static/img/logos/sqlite.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
static/img/logos/svelte.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
24
svelte.config.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import adapter from '@sveltejs/adapter-static';
|
||||||
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
// Consult https://svelte.dev/docs/kit/integrations
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
|
kit: {
|
||||||
|
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||||
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
|
adapter: adapter({
|
||||||
|
pages: 'build',
|
||||||
|
assets: 'build',
|
||||||
|
fallback: 'index.html',
|
||||||
|
precompress: false,
|
||||||
|
strict: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
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://svelte.dev/docs/kit/configuration#alias
|
||||||
|
// except $lib which is handled by https://svelte.dev/docs/kit/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
|
||||||
|
}
|
6
vite.config.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit({})],
|
||||||
|
});
|