git commit -am "Pruned git history"
All checks were successful
Rebuild signaller for deprived.dev to rebuild site / Rebuild Signaller (push) Successful in 21s

This commit is contained in:
BOTAlex 2026-05-05 13:42:47 +02:00
commit 158918c0f5
201 changed files with 24558 additions and 0 deletions

271
src/routes/+layout.svelte Normal file
View file

@ -0,0 +1,271 @@
<!-- If url contains "hideOnPrint" param, then detect if start printing then hide elements -->
<script lang="ts">
import "../app.css";
import fly from "@e/fly";
import MediaQuery from "svelte-media-queries";
import Dices from "@lucide/svelte/icons/dices";
import re from "@ts/Redaction/Redactor";
let hideOnPrint: boolean = $state(false);
let { children } = $props();
import DeprivedLogo from "$lib/images/DeprivedLogo.svelte";
import HamburgerMenuIcon from "$lib/images/HamburgerMenuIcon.svelte";
const footerCollapseThreshold: string = "40rem";
const headerCollapseThreshold: string = "40rem";
let footerCollapse: boolean;
let isMobile: boolean = $state(false);
let navbarHidden: boolean = $state(true);
let isDevUrl = $state(false);
function resetNavBar() {
navbarHidden = true;
}
function handleUrlParams() {
const params = new URLSearchParams(window.location.search);
hideOnPrint = params.get("hideOnPrint") === "1";
if (!!params.get("key")) {
localStorage.setItem("key", params.get("key")!);
}
}
import { afterNavigate } from "$app/navigation";
afterNavigate(() => {
handleUrlParams();
});
import onMount from "@e/onMount";
import Zooter from "./comps/Zooter.svelte";
import CustomScrollBar from "./comps/CustomScrollBar.svelte";
onMount(async () => {
handleUrlParams();
const lock = document.createElement("meta");
lock.name = "darkreader-lock";
document.head.appendChild(lock);
await re.TryGetUnredacter();
const { hostname } = window.location;
isDevUrl = hostname.includes("dev") || hostname.includes("localhost");
});
function nextTheme() {
let theme: string | null = null;
if (typeof localStorage !== "undefined") {
theme = localStorage.getItem("theme");
}
const themesArr = (window as any).AvailableThemes;
let nextTheme = "dark";
if (
theme == "null" ||
theme == "undefined" ||
theme == undefined ||
theme == null
) {
} else {
nextTheme = themesArr[(1 - -themesArr.indexOf(theme)) % themesArr.length];
}
console.log("Slecting: " + nextTheme);
document.documentElement.setAttribute("data-theme", nextTheme);
localStorage.setItem("theme", nextTheme);
}
</script>
{#snippet SwitchThemeButton()}
<div
class="tooltip tooltip-bottom grid place-content-center"
data-tip="Switch theme"
>
<button class="cursor-pointer" onclick={nextTheme}> <Dices /></button>
</div>
{/snippet}
<!-- Detect mobile -->
<MediaQuery
query="(max-width: {footerCollapseThreshold})"
bind:matches={footerCollapse}
/>
<MediaQuery
query="(max-width: {headerCollapseThreshold})"
bind:matches={isMobile}
/>
<CustomScrollBar
overflowX="hidden"
overflowY="auto"
Class="h-screen {hideOnPrint ? 'hide-on-print' : ''}"
requireAbsolute={true}
hideOnMobile={true}
>
<div class="flex flex-col justify-between min-h-screen bg-base-200 p-0">
<header class="{hideOnPrint ? 'hide-on-print' : ''} bg-base-300">
<div class="nav-bar pr-4">
<!-- TODO: Make this one element instead of this weird ass if statement -->
{#if !isMobile}
<div class="desktop items-center">
<a href="/" class="nav-head">
<DeprivedLogo
Class="fill-base-content p-2"
Style="width: 3.5rem; height: auto;"
/>
<!-- <h3 id="logo-text">The Deprived Devs</h3> -->
</a>
<div class="nav-spacer" />
{@render SwitchThemeButton()}
<a
href="/cv?hideOnPrint=1"
target="_blank"
style="width: 7.5rem;"
class="text-center justify-center text-md"
>{$re?.nick ?? "Alex"}'s CV</a
>
<!-- <a href="/tools" style="width: 7.5rem;" class="text-center">Tools</a> -->
<!-- <a href="https://botalex.itch.io/" target="_blank">Games</a> -->
<!-- <a href="/posts">Blog</a>
<a href="/about">About</a> -->
</div>
{:else}
<div class="collapsed shadow-xl">
<a onclick={resetNavBar} href="/" class="nav-head">
<DeprivedLogo
Class="fill-base-content p-2"
Style="width: 3.5rem; height: auto;"
/>
<!-- <h3 id="logo-text">The Deprived Devs</h3> -->
</a>
<div class="nav-spacer" />
{@render SwitchThemeButton()}
<div class="px-1"></div>
<button
id="toggle-nav"
onclick={() => {
navbarHidden = !navbarHidden;
console.log(navbarHidden);
}}
>
<HamburgerMenuIcon Class="fill-base-content" />
</button>
</div>
{#if !navbarHidden}
<div class="nav-list" transition:fly={{ y: -25, duration: 350 }}>
<!-- <a onclick={resetNavBar} href="/">Home</a> -->
<!-- <a -->
<!-- onclick={resetNavBar} -->
<!-- href="https://botalex.itch.io/" -->
<!-- target="_blank">Games</a -->
<!-- > -->
<a href="/cv?hideOnPrint=1" target="_blank" class="justify-center"
>{$re?.nick ?? "Alex"}'s CV</a
>
<!-- <a onclick={resetNavBar} href="/posts">Blog</a>
<a onclick={resetNavBar} href="/about">About</a> -->
</div>
{/if}
{/if}
</div>
</header>
<div class="flex-1">
<!-- Page content -->
{@render children?.()}
</div>
<Zooter bind:hideOnPrint />
</div>
</CustomScrollBar>
{#if hideOnPrint}
<div class="flex-1 hide-if-not-print">
<!-- Page content -->
{@render children?.()}
</div>
{/if}
{#if footerCollapse}
<style>
.about-container {
flex-direction: column;
justify-content: center !important;
gap: 25px;
}
</style>
{/if}
<style>
/* Nav bar. */
header {
display: flex;
justify-content: center;
}
.back-to-top {
opacity: 1;
right: 16px;
user-select: none;
bottom: 80px;
}
header a {
text-decoration: none;
}
.nav-bar {
width: 100%;
max-width: 1400px;
}
.desktop {
width: 100%;
display: flex;
gap: 30px;
}
.collapsed {
width: 100%;
display: flex;
}
#toggle-nav {
background: transparent;
border: none;
}
.nav-list {
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
}
.nav-head {
display: flex;
align-items: center;
gap: 10px;
}
.nav-spacer {
width: 100%;
}
header a {
display: flex;
align-items: center;
font-size: 22px;
font-family: var(--title-font);
color: oklch(var(--bc));
}
a:hover {
filter: brightness(130%);
}
</style>

1
src/routes/+layout.ts Normal file
View file

@ -0,0 +1 @@
export const prerender = true;

353
src/routes/+page.svelte Normal file
View file

@ -0,0 +1,353 @@
<script lang="ts">
import MediaQuery from "svelte-media-queries";
import onMount from "@e/onMount";
import PreviewDeprivedLogo from "$lib/images/DeprivedLogo-NoBackground.png";
import BackgroundVideo from "$lib/videos/DeprivedDevMontage.gif";
import Profile from "./comps/Profile.svelte";
import DeprivedTrackerSection from "./comps/DeprivedTrackerSection.svelte";
import re from "@src/ts/Redaction/Redactor";
const mobileThreshold: string = "600px"; // was 1000px.
let mobile: boolean;
let debug = false;
onMount(() => {
let tabTittleElement = window.document.getElementById("TabTittle");
if (tabTittleElement)
// Not null
tabTittleElement.innerHTML = "Deprived devs";
const params = new URLSearchParams(window.location.search);
debug = params.has("debug");
});
</script>
<!-- Detect mobile -->
<MediaQuery query="(max-width: {mobileThreshold})" bind:matches={mobile} />
<div class="h-full w-full">
<title id="TabTittle">We are the DEPRIVED DEVS</title>
<meta content="We are the deprived devs" property="og:title" />
<meta
content="We make everything frontend, backend, games, websites, machine learning, whatever. We're just abunch of nerds, and we love it!"
property="og:description"
/>
<meta content={PreviewDeprivedLogo} property="og:image" />
<meta content="#bdd6ee" data-react-helmet="true" name="theme-color" />
<div
class="relative pointer-events-auto flex overflow-hidden w-full h-[70vh]"
>
<div class=" flex w-full h-full">
<img
class="w-full h-full object-cover filter blur-sm brightness-60"
src={BackgroundVideo}
alt="Background video"
/>
</div>
<div
class="absolute left-0 top-0 w-full h-full flex justify-center items-center"
>
<h1
style="font-size: {!mobile
? 5
: 3}rem; text-shadow: 0.2rem 0.2rem 1rem rgba(0, 0, 0, 0.9);"
>
{#if !mobile}
Deprived Devs
{:else}
Deprived
<br />
<span class="-mt-6 prose" style="font-size: 2rem;"> Devs </span>
{/if}
</h1>
{#if mobile}
<div style="width: 100px; height: 100px;"></div>
{/if}
</div>
</div>
<DeprivedTrackerSection />
<div
class="cozette flex flex-col justify-center items-center h-full w-full md:px-8 pt-4"
>
<h2 class="text-center" style="font-size: {!mobile ? 3 : 2}rem;">
Developers
</h2>
<div class="">
<div
class="grid max-lg:grid-cols-1 sm:grid-cols-2 gap-4 p-4 max-lg:px-0 w-full"
>
<Profile
name={$re?.nick ? $re?.nick + "/Alex" : "Alex"}
tags={["Programmer", "3D artist", "UX Designer"]}
isMobile={mobile}
>
<span>
<p>
Hi, I am {$re?.nick ? $re?.nick + "/Alex" : "Alex"}, {@html !mobile
? ""
: "<br/>"} I'm known as BOTAlex online
</p>
<p>
Here's my CV: <a href="/cv?hideOnPrint=1" style="color:lightblue;"
>pdf</a
>
</p>
</span>
</Profile>
<Profile
name="Sveske / Benjamin"
tags={["Programmer", "Back-end Admin"]}
isMobile={mobile}
>
<span>
<p>
<span class="inline line-through">Hi, I use Arch, btw. </span> I use
NixOS now
</p>
<p>
<!-- <a -->
<!-- href="https://www.linkedin.com/in/benjamin-dreyer/" -->
<!-- target="_blank" -->
<!-- style="color:lightblue;">Linked-in</a -->
<!-- > -->
</p>
</span>
</Profile>
<Profile isSnorre={true} tags={["Programmer"]} isMobile={mobile} />
<Profile
replaced={true}
name="Oliver"
tags={["Sound/Story", "2D Artist", "Programmer"]}
isMobile={mobile}
>
<span>
<p>Snorre does not get paid.</p>
<p>
<!-- <a -->
<!-- href="https://www.linkedin.com/in/oliver-schwenger-291944278/" -->
<!-- target="_blank" -->
<!-- style="color:lightblue;">Linked-in</a -->
<!-- > -->
<br />
</p>
</span>
</Profile>
<Profile
replaced={true}
name="Kim"
tags={["Cinemachine", "3D Artist", "Programmer"]}
isMobile={mobile}
>
<span>
<p>Abla espaniol</p>
<p>
<!-- <a -->
<!-- href="https://www.linkedin.com/in/kim-rex-de-dios-408860299/" -->
<!-- target="_blank" -->
<!-- style="color:lightblue;">Linked-in</a -->
<!-- > -->
<br />
</p>
</span>
</Profile>
<Profile
replaced={true}
name="Zylvester"
tags={["Sound/Story", "2D/3D artist"]}
isMobile={mobile}
>
<span>
<p>Used to draw furry commisions (Wasted potential)</p>
<p>
<!-- <a -->
<!-- href="https://www.linkedin.com/in/sylvester-junge-0b2a73196/" -->
<!-- target="_blank" -->
<!-- style="color:lightblue;">Linked-in</a -->
<!-- >, -->
<!-- <a -->
<!-- href="https://www.youtube.com/watch?v=xvFZjo5PgG0" -->
<!-- style="color:lightblue;">Funny link</a -->
<!-- > -->
<br />
</p>
</span>
</Profile>
</div>
<span class="opacity-20">¹ They don't do shit</span>
</div>
</div>
<div class="py-4"></div>
<div
class="grid place-content-center place-items-center pointer-events-auto font-mono"
>
<!-- <article class="pt-16 prose overflow-hidden {mobile ? "px-8" : ""}">
<h2 class="main-title {!mobile ? "text-center m-auto" : "m-0"}" style="font-size: {!mobile ? 3 : 3}rem; ">About us</h2>
<p>We are a small group of developers and artists who started out as classmates, united by our passion for all things technology.</p>
</article> -->
<!-- Spacer -->
<!-- <div style="width: 50%;" class="{!mobile ? "py-16" : "py-4"}">
<ProfileSpacer/>
</div> -->
<!-- <article class="prose {mobile ? 'px-8' : ''}"> -->
<!-- <h2 -->
<!-- class="main-title {!mobile ? 'text-center m-auto' : 'm-0'}" -->
<!-- style="font-size: {!mobile ? 3 : 3}rem; " -->
<!-- > -->
<!-- Games -->
<!-- </h2> -->
<!-- <p> -->
<!-- Here are some of our games from various gamejams from the past. <br -->
<!-- />(<span class="font-bold">ONLY</span> 48 hours per game) -->
<!-- </p> -->
<!-- </article> -->
<!---->
<!-- Spacer -->
<!-- <div style="width: 50%;" class={!mobile ? "py-8" : "py-4"}></div> -->
<!---->
<!-- <div -->
<!-- class="grid grid-flow-row gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4" -->
<!-- > -->
<!-- Corro rebounce -->
<!-- <div class="games card bg-base-100 shadow-xl"> -->
<!-- <figure style="height: 15em;"> -->
<!-- <Carousel images={[Corrobot1, Corrobot2, Corrobot3]} /> -->
<!-- </figure> -->
<!-- <div class="card-body"> -->
<!-- <h2 class="card-title">Corrobot-rebounce</h2> -->
<!-- <p>A 3D sequel to Corrobot-Takeover</p> -->
<!-- <br /> -->
<!-- <p> -->
<!-- This was made during <a -->
<!-- href="https://itch.io/jam/nordic-game-jam-2024/rate/2659665" -->
<!-- class="underline">Nordic gamejam 2024</a -->
<!-- > -->
<!-- </p> -->
<!-- <div class="card-actions justify-end"> -->
<!-- <a -->
<!-- href="https://botalex.itch.io/corrobot-rebounce" -->
<!-- target="_blank" -->
<!-- class="btn btn-primary text-primary-content">View on itch.io</a -->
<!-- > -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
<!---->
<!-- Blood -->
<!-- <div class="games card bg-base-100 w-96 shadow-xl"> -->
<!-- <figure style="height: 15em;"> -->
<!-- <Carousel images={[Blood1, Blood2, Blood3, Blood4, Blood5]} /> -->
<!-- </figure> -->
<!-- <div class="card-body"> -->
<!-- <h2 class="card-title">Unnamed blood game</h2> -->
<!-- <p>A game based on an unique kind of combat</p> -->
<!-- <br /> -->
<!-- <p> -->
<!-- This was made during <a -->
<!-- href="https://itch.io/jam/future-game-makers-jam-2024" -->
<!-- class="underline">Future Game Makers</a -->
<!-- >, and of course our team won the competition. -->
<!-- </p> -->
<!-- <div class="card-actions justify-end"> -->
<!-- <a -->
<!-- href="https://botalex.itch.io/mop-of-the-dead" -->
<!-- target="_blank" -->
<!-- class="btn btn-primary text-primary-content">View on itch.io</a -->
<!-- > -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
<!---->
<!-- Time -->
<!-- <div class="games card bg-base-100 w-96 shadow-xl"> -->
<!-- <figure style="height: 15em;"> -->
<!-- <Carousel images={[Time1, Time2, Time3, Time4, Time5]} /> -->
<!-- </figure> -->
<!-- <div class="card-body"> -->
<!-- <h2 class="card-title">One More Time</h2> -->
<!-- <p> -->
<!-- What if time was money? A rougelike where you need to kill for time, -->
<!-- which you can choose to spend. -->
<!-- </p> -->
<!-- <br /> -->
<!-- <p> -->
<!-- This was made during <a -->
<!-- href="https://itch.io/jam/dmspiljam-november-2021" -->
<!-- class="underline">Denmark Masters jam</a -->
<!-- >. This jam has youths allover Denmark to compete, and of course our -->
<!-- team won the competition again. -->
<!-- </p> -->
<!-- <div class="card-actions justify-end"> -->
<!-- <a -->
<!-- href="https://botalex.itch.io/one-more-time" -->
<!-- target="_blank" -->
<!-- class="btn btn-primary text-primary-content">View on itch.io</a -->
<!-- > -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
<!---->
<!-- <div class="games card bg-base-100 w-96 shadow-xl"> -->
<!-- <figure class="rounded-b-none" style="height: 15em;"> -->
<!-- <div class="bg-grid-100 flex w-full h-full"></div> -->
<!-- </figure> -->
<!-- <div class="card-body"> -->
<!-- <h2 class="card-title">What's next?</h2> -->
<!-- <div class="skeleton mt-1 h-4 w-28"></div> -->
<!-- <div class="skeleton h-4 w-full"></div> -->
<!-- <div class="skeleton h-4 w-full"></div> -->
<!-- <div class="skeleton h-4 w-28"></div> -->
<!-- <div class="skeleton h-4 w-full"></div> -->
<!-- <div class="flex grow" /> -->
<!-- <div class="card-actions justify-end"> -->
<!-- <a -->
<!-- href="/" -->
<!-- target="_blank" -->
<!-- class="btn btn-primary text-primary-content text-primary-content" -->
<!-- >RECURSION!</a -->
<!-- > -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
</div>
</div>
{#if !mobile}
<style>
.games {
width: 24rem /* 384px */;
}
</style>
{:else}
<style>
.games {
width: 80%;
display: flex;
justify-self: center;
}
</style>
{/if}
<style>
#backgroundGif {
width: 100%;
height: 100%;
max-height: 40vh;
object-fit: cover;
filter: blur(5px) brightness(0.6);
}
</style>

View file

@ -0,0 +1,133 @@
<script lang="ts">
import onMount from "@e/onMount";
export let images: string[] = []; // Expose images as a parameter
let currentIndex: number = 0;
let startX: number | null = null; // Track touch start X position
let deltaX: number = 0; // Track touch delta X
const nextSlide = (): void => {
currentIndex = (currentIndex + 1) % images.length;
};
const prevSlide = (): void => {
currentIndex = (currentIndex - 1 + images.length) % images.length;
};
const handleTouchStart = (event: TouchEvent): void => {
startX = event.touches[0].clientX;
};
const handleTouchMove = (event: TouchEvent): void => {
if (startX !== null) {
deltaX = event.touches[0].clientX - startX;
}
};
const handleTouchEnd = (): void => {
if (startX !== null) {
if (deltaX > 50) {
prevSlide(); // Swipe right
} else if (deltaX < -50) {
nextSlide(); // Swipe left
}
}
// Reset touch variables
startX = null;
deltaX = 0;
};
</script>
<style>
.carousel {
position: relative;
overflow: hidden;
width: 100%;
max-width: 800px;
height: 100%;
margin: auto;
}
.slides {
display: flex;
transition: transform 0.5s ease-in-out;
width: 100%;
}
.slide {
flex: 0 0 100%;
object-fit: cover;
object-position: center;
}
.controls {
position: absolute;
top: 50%;
width: 100%;
display: flex;
justify-content: space-between;
transform: translateY(-50%);
}
.control {
background: rgba(0, 0, 0, 0.5);
border: none;
color: white;
font-size: 1.5rem;
cursor: pointer;
padding: 0.5rem;
}
.indicators {
display: flex;
justify-content: center;
position: absolute;
bottom: 10px;
width: 100%;
}
.indicator {
width: 10px;
height: 10px;
margin: 0 5px;
background: white;
border-radius: 50%;
opacity: 0.5;
cursor: pointer;
}
.indicator.active {
opacity: 1;
}
</style>
<div
class="carousel"
on:touchstart|preventDefault={handleTouchStart}
on:touchmove|preventDefault={handleTouchMove}
on:touchend|preventDefault={handleTouchEnd}
>
<div
class="slides"
style="transform: translateX(-{currentIndex * 100}%);"
>
{#each images as image (image)}
<img class="slide" src={image} alt="Carousel Slide" />
{/each}
</div>
<div class="controls">
<button class="control" on:click={prevSlide}>&lt;</button>
<button class="control" on:click={nextSlide}>&gt;</button>
</div>
<div class="indicators">
{#each images as _, index}
<div
class="indicator {index === currentIndex ? 'active' : ''}"
on:click={() => (currentIndex = index)}
></div>
{/each}
</div>
</div>

View file

@ -0,0 +1,464 @@
<script lang="ts">
import onMount from "@e/onMount";
import onDestroy from "@e/onDestroy";
import MediaQuery from "svelte-media-queries";
// Public props
export let overflowX: "auto" | "scroll" | "hidden" = "hidden";
export let overflowY: "auto" | "scroll" | "hidden" = "auto";
export let hideOnMobile = false; // True if hide scrollbar when mobile detected
// Visual tuning
export let thickness = 12; // px
export let padding = 0; // px, space around track inside the overlay
export let minThumb = 10; // px
export let contentPadding = 0; // px, inner padding for your content
export let Class = ""; // Extra classes for the scrollbar
// Styling (customize freely)
export let trackStyle = "";
export let trackClass = "";
export let trackOpacity = 0.55;
export let thumbClass =
"bg-black opacity-50 border-white text-xs text-center text-opacity-50 overflow-hidden flex items-center text-nowrap justify-center";
export let thumbLength = 20; // px. doesn't work for some reason, idk
export let requireAbsolute = false; // Some needs absolute for some reason. idk
export let scrollThumbText =
"-------------------------------------------------------------------------------------------------------------------------------Scroll-------------------------------------------------------------------------------------------------------------------------------";
let hideOnPrint = false;
let viewport: HTMLDivElement;
let vBar: HTMLDivElement; // vertical bar container
let hBar: HTMLDivElement; // horizontal bar container
let vThumb: HTMLDivElement;
let hThumb: HTMLDivElement;
let showBarY = false;
let showBarX = false;
let isMobile = false;
// ——— utils
const sMaxY = () =>
Math.max(0, viewport.scrollHeight - viewport.clientHeight);
const sMaxX = () => Math.max(0, viewport.scrollWidth - viewport.clientWidth);
const tMaxY = () =>
Math.max(0, (vBar?.clientHeight || 0) - (vThumb?.offsetHeight || 0));
const tMaxX = () =>
Math.max(0, (hBar?.clientWidth || 0) - (hThumb?.offsetWidth || 0));
function updateVisibility() {
showBarY =
overflowY !== "hidden" &&
viewport.scrollHeight > viewport.clientHeight &&
!(hideOnMobile && isMobile);
showBarX =
overflowX !== "hidden" &&
viewport.scrollWidth > viewport.clientWidth &&
!(hideOnMobile && isMobile);
}
function updateVerticalThumb() {
if (!vThumb || !vBar) return;
const ratio = viewport.clientHeight / viewport.scrollHeight;
const h = Math.max(minThumb, Math.round(ratio * vBar.clientHeight));
vThumb.style.height = `${h}px`;
const top = sMaxY() ? (viewport.scrollTop / sMaxY()) * tMaxY() : 0;
vThumb.style.top = `${top}px`;
vThumb.setAttribute("aria-valuemin", "0");
vThumb.setAttribute("aria-valuemax", String(sMaxY()));
vThumb.setAttribute("aria-valuenow", String(viewport.scrollTop));
}
function updateHorizontalThumb() {
if (!hThumb || !hBar) return;
const ratio = viewport.clientWidth / viewport.scrollWidth;
const w = Math.max(minThumb, Math.round(ratio * hBar.clientWidth));
hThumb.style.width = `${w}px`;
const left = sMaxX() ? (viewport.scrollLeft / sMaxX()) * tMaxX() : 0;
hThumb.style.left = `${left}px`;
hThumb.setAttribute("aria-valuemin", "0");
hThumb.setAttribute("aria-valuemax", String(sMaxX()));
hThumb.setAttribute("aria-valuenow", String(viewport.scrollLeft));
}
function updateAll() {
updateVisibility();
updateVerticalThumb();
updateHorizontalThumb();
}
// Drag state
let draggingV = false;
let draggingH = false;
let dragStart = { x: 0, y: 0, scrollLeft: 0, scrollTop: 0 };
let pointerIdV: number | null = null;
let pointerIdH: number | null = null;
function onVPointerDown(e: PointerEvent) {
draggingV = true;
pointerIdV = e.pointerId;
vThumb.setPointerCapture(e.pointerId);
dragStart = {
x: e.clientX,
y: e.clientY,
scrollLeft: viewport.scrollLeft,
scrollTop: viewport.scrollTop,
};
e.preventDefault();
}
function onVPointerMove(e: PointerEvent) {
if (!draggingV) return;
const dy = e.clientY - dragStart.y;
const pixelsPerThumb = sMaxY() / Math.max(1, tMaxY());
viewport.scrollTop = Math.min(
sMaxY(),
Math.max(0, dragStart.scrollTop + dy * pixelsPerThumb),
);
}
function endVDrag(e: PointerEvent) {
if (!draggingV) return;
draggingV = false;
if (pointerIdV != null) {
try {
vThumb.releasePointerCapture(pointerIdV);
} catch {}
pointerIdV = null;
}
}
function onHPointerDown(e: PointerEvent) {
draggingH = true;
pointerIdH = e.pointerId;
hThumb.setPointerCapture(e.pointerId);
dragStart = {
x: e.clientX,
y: e.clientY,
scrollLeft: viewport.scrollLeft,
scrollTop: viewport.scrollTop,
};
e.preventDefault();
}
function onHPointerMove(e: PointerEvent) {
if (!draggingH) return;
const dx = e.clientX - dragStart.x;
const pixelsPerThumb = sMaxX() / Math.max(1, tMaxX());
viewport.scrollLeft = Math.min(
sMaxX(),
Math.max(0, dragStart.scrollLeft + dx * pixelsPerThumb),
);
}
function endHDrag(e: PointerEvent) {
if (!draggingH) return;
draggingH = false;
if (pointerIdH != null) {
try {
hThumb.releasePointerCapture(pointerIdH);
} catch {}
pointerIdH = null;
}
}
// Track clicks: jump to position
function onVTrackPointerDown(e: PointerEvent) {
if (e.target === vThumb) return;
const rect = vBar.getBoundingClientRect();
const y = e.clientY - rect.top - vThumb.offsetHeight / 2;
const clamped = Math.min(tMaxY(), Math.max(0, y));
viewport.scrollTop = (clamped / Math.max(1, tMaxY())) * sMaxY();
}
function onHTrackPointerDown(e: PointerEvent) {
if (e.target === hThumb) return;
const rect = hBar.getBoundingClientRect();
const x = e.clientX - rect.left - hThumb.offsetWidth / 2;
const clamped = Math.min(tMaxX(), Math.max(0, x));
viewport.scrollLeft = (clamped / Math.max(1, tMaxX())) * sMaxX();
}
// Keyboard on thumbs
function onVKeyDown(e: KeyboardEvent) {
const step = 40;
const page = Math.max(120, Math.floor(viewport.clientHeight * 0.9));
switch (e.key) {
case "ArrowDown":
viewport.scrollBy({ top: step });
e.preventDefault();
break;
case "ArrowUp":
viewport.scrollBy({ top: -step });
e.preventDefault();
break;
case "PageDown":
viewport.scrollBy({ top: page });
e.preventDefault();
break;
case "PageUp":
viewport.scrollBy({ top: -page });
e.preventDefault();
break;
case "Home":
viewport.scrollTo({ top: 0 });
e.preventDefault();
break;
case "End":
viewport.scrollTo({ top: sMaxY() });
e.preventDefault();
break;
}
}
function onHKeyDown(e: KeyboardEvent) {
const step = 40;
const page = Math.max(120, Math.floor(viewport.clientWidth * 0.9));
switch (e.key) {
case "ArrowRight":
viewport.scrollBy({ left: step });
e.preventDefault();
break;
case "ArrowLeft":
viewport.scrollBy({ left: -step });
e.preventDefault();
break;
case "PageDown":
viewport.scrollBy({ left: page });
e.preventDefault();
break;
case "PageUp":
viewport.scrollBy({ left: -page });
e.preventDefault();
break;
case "Home":
viewport.scrollTo({ left: 0 });
e.preventDefault();
break;
case "End":
viewport.scrollTo({ left: sMaxX() });
e.preventDefault();
break;
}
}
let ro: ResizeObserver;
onMount(() => {
const params = new URLSearchParams(window.location.search);
hideOnPrint = params.get("hideOnPrint") === "1";
const onScroll = () => {
updateVerticalThumb();
updateHorizontalThumb();
};
viewport.addEventListener("scroll", onScroll, { passive: true });
ro = new ResizeObserver(() => updateAll());
ro.observe(viewport);
if (viewport.firstElementChild instanceof HTMLElement) {
ro.observe(viewport.firstElementChild);
}
// Global pointer end (in case drag ends outside)
const endAll = () => {
draggingV = draggingH = false;
};
window.addEventListener("pointerup", endAll);
// Initial paint
queueMicrotask(updateAll);
return () => {
viewport.removeEventListener("scroll", onScroll as any);
window.removeEventListener("pointerup", endAll);
};
});
onDestroy(() => {
ro?.disconnect();
});
// Padding for content so the overlay bars dont cover it
$: pr = contentPadding + (showBarY ? thickness + padding * 2 : 0);
$: pb = contentPadding + (showBarX ? thickness + padding * 2 : 0);
</script>
<MediaQuery query="(max-width: 40rem)" bind:matches={isMobile} />
<!-- svelte-ignore element_invalid_self_closing_tag -->
<!-- svelte-ignore a11y_role_has_required_aria_props -->
<!-- Wrapper -->
<div
class="relative {overflowY == 'hidden'
? ''
: 'overflow-y-hidden'} {overflowX == 'hidden'
? ''
: 'overflow-x-hidden'} {Class}"
>
<!-- The real, native scrolling area (we hide its native scrollbar) -->
<div
bind:this={viewport}
class="csb-viewport {requireAbsolute
? 'absolute'
: ''} inset-0 overflow-auto focus:outline-none"
style="
padding: {contentPadding}px {pr}px {pb}px {contentPadding}px;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
"
>
<div class="min-w-0 min-h-0">
<slot />
</div>
</div>
<!-- Vertical overlay bar (mirrors x-scroll structure) -->
{#if showBarY}
<div
bind:this={vBar}
class="absolute bg-base-200 {hideOnPrint ? 'hide-on-print' : ''} "
style="
top: {padding}px;
bottom: {padding}px;
right: {padding}px;
width: {thickness}px;
pointer-events: none;
"
aria-hidden="false"
>
<div class="absolute inset-0 corner-border-container">
<div
class="transition-opacity {trackClass} w-full h-full"
style="
pointer-events: auto;
{trackStyle}
"
on:pointerdown={onVTrackPointerDown}
/>
</div>
<div
bind:this={vThumb}
role="scrollbar"
aria-orientation="vertical"
tabindex="0"
class={`absolute border-y-2 left-0 right-0 rounded-none cursor-grab active:cursor-grabbing focus-visible:outline focus-visible:outline-2 focus-visible:outline-sky-500 ${thumbClass}`}
style="height: {thumbLength}px; pointer-events: auto; touch-action: none;"
on:pointerdown={onVPointerDown}
on:pointermove={onVPointerMove}
on:pointerup={endVDrag}
on:pointercancel={endVDrag}
on:keydown={onVKeyDown}
>
<span class="rotate-90">
{scrollThumbText}
</span>
</div>
</div>
{/if}
<!-- Horizontal overlay bar -->
{#if showBarX}
<div
bind:this={hBar}
class="absolute {hideOnPrint ? 'hide-on-print' : ''} "
style="
left: {padding}px;
right: {padding}px;
bottom: {padding}px;
height: {thickness}px;
pointer-events: none;
"
aria-hidden="false"
>
<div class="absolute inset-0 corner-border-container">
<div
class="transition-opacity {trackClass} h-full"
style="
pointer-events: auto;
{trackStyle}
"
on:pointerdown={onHTrackPointerDown}
/>
</div>
<div
bind:this={hThumb}
role="scrollbar"
aria-orientation="horizontal"
tabindex="0"
class={`absolute border-x-2 top-0 bottom-0 rounded-none cursor-grab active:cursor-grabbing focus-visible:outline focus-visible:outline-2 focus-visible:outline-sky-500 ${thumbClass}`}
style="width: {thumbLength}px; pointer-events: auto; touch-action: none;"
on:pointerdown={onHPointerDown}
on:pointermove={onHPointerMove}
on:pointerup={endHDrag}
on:pointercancel={endHDrag}
on:keydown={onHKeyDown}
>
{scrollThumbText}
</div>
</div>
{/if}
</div>
<style lang="scss">
/* Hide the native scrollbars while keeping native scrolling */
:global(.csb-viewport) {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge legacy */
}
:global(.csb-viewport::-webkit-scrollbar) {
width: 0;
height: 0; /* WebKit */
}
.corner-border-container {
--length: 5px;
--width: 1px;
--line-color: #eeeeee;
--background-color: #0002;
background-color: var(--background-color);
background-image:
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color));
background-size:
var(--length) var(--width),
var(--width) var(--length),
var(--length) var(--width),
var(--width) var(--length),
var(--length) var(--width),
var(--width) var(--length),
var(--length) var(--width),
var(--width) var(--length);
background-position:
top left,
top left,
top right,
top right,
bottom right,
bottom right,
bottom left,
bottom left;
background-repeat: no-repeat;
border-radius: 0;
& > div {
background: repeating-linear-gradient(
-45deg,
#0000,
#0000 15px,
#fff2 10px 20px
);
}
}
</style>

View file

@ -0,0 +1,63 @@
<script>
import onMount from "@e/onMount";
let debug = false;
onMount(()=> {
let tabTittleElement = window.document.getElementById("TabTittle");
if (tabTittleElement) // Not null
tabTittleElement.innerHTML = "Deprived devs";
const params = new URLSearchParams(window.location.search);
debug = params.has('debug');
});
</script>
<!-- Desktop -->
<div class="{debug? "" : "hidden"} max-md:hidden cozette flex flex-col justify-center items-center w-full md:px-8 pt-4">
<div class="grid grid-cols-2 items-center">
<div>
<img src="https://placehold.co/400x400/000000/FFFFFF" alt="">
</div>
<div class="flex flex-col">
<div>
<h2 class="font-semibold text-base-content text-xl">Deprived Trackers</h2>
<span class="text-base-content">
Hopefully the cheapest Smol-type SlimeVR Trackers on the market.
</span>
</div>
<div class="py-2"></div>
<div class="flex justify-center gap-2">
<a href="/" class="btn btn-secondary">Buy</a>
<a href="/shop/trackers" class="btn btn-primary">Learn more</a>
</div>
</div>
</div>
</div>
<!-- Mobile -->
<div class="{debug? "" : "hidden"} md:hidden cozette flex flex-col justify-center items-center w-full md:px-8 pt-4">
<div class="relative grid items-center max-w-64">
<div class="blur-xs">
<img src="https://placehold.co/400x400/000000/FFFFFF" alt="">
</div>
<div class="absolute flex flex-col justify-between h-full p-8">
<div>
<h2 class="font-semibold text-base-content">Deprived Trackers</h2>
<span class="text-base-content">
Hopefully the cheapest Smol-type SlimeVR Trackers on the market.
</span>
</div>
<div class="py-2"></div>
<div class="flex justify-center gap-2">
<a href="/" class="btn btn-secondary">Buy</a>
<a href="/shop/trackers" class="btn btn-primary">Learn more</a>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,16 @@
<script>
export let Title = "";
export let Checked = false;
</script>
<div class="collapse collapse-arrow bg-base-100 text-start shadow-sm" {...$$restProps}>
{#if Checked}
<input type="radio" name="{Title}" checked/>
{:else}
<input type="radio" name="{Title}"/>
{/if}
<div class="collapse-title text-xl font-medium "><b>{Title}</b></div>
<div class="collapse-content" style="border-top: dotted 0.15rem oklch(var(--b3));">
<slot/>
</div>
</div>

View file

@ -0,0 +1,10 @@
<script>
import Tags from "./Tags.svelte";
export let isMobile = false;
export let tags = ["null"];
</script>
{#if isMobile}
<Tags Tags={tags} isMobile={isMobile}/>
{/if}

View file

@ -0,0 +1,15 @@
<script>
import Tags from "./Tags.svelte"; // Should've used better names lol
export let isMobile = false;
export let name = "";
export let tags = ["null"];
</script>
<div class="flex items-center">
<h2 style="font-size: {!isMobile ? 1.5 : 1.5}rem;">{name}</h2>
<div class="flex flex-grow"/>
{#if !isMobile}
<Tags Tags={tags} isMobile={isMobile}/>
{/if}
</div>

View file

@ -0,0 +1,61 @@
<script lang="ts">
import Carousel from './Carousel.svelte';
export let title: string;
export let description: string;
export let jam = { name: '', url: '' };
export let viewUrl : string= '';
export let images: string[] = [];
export let placeholder = false;
</script>
<div class="games card bg-base-100 shadow-xl {placeholder ? 'w-96' : ''}">
{#if !placeholder}
<figure style="height: 15em;">
<Carousel {images} />
</figure>
<div class="card-body">
<h2 class="card-title">{title}</h2>
<p>{description}</p>
<br />
<p>
This was made during
<a href={jam.url} class="underline" target="_blank" rel="noopener">
{jam.name}
</a>
</p>
<div class="card-actions justify-end">
<a href={viewUrl}
class="btn btn-primary text-primary-content"
target="_blank"
rel="noopener"
>
View on itch.io
</a>
</div>
</div>
{:else}
<figure class="rounded-b-none" style="height: 15em;">
<div class="bg-grid-100 flex w-full h-full"></div>
</figure>
<div class="card-body">
<h2 class="card-title">What's next?</h2>
<div class="skeleton mt-1 h-4 w-28"></div>
<div class="skeleton h-4 w-full"></div>
<div class="skeleton h-4 w-full"></div>
<div class="skeleton h-4 w-28"></div>
<div class="skeleton h-4 w-full"></div>
<div class="flex grow"></div>
<div class="card-actions justify-end">
<a href="/"
class="btn btn-primary text-primary-content"
target="_blank"
rel="noopener"
>
RECURSION!
</a>
</div>
</div>
{/if}
</div>

View file

@ -0,0 +1,68 @@
<script>
import PostCard from './PostCard.svelte';
import Corrobot1 from '@images/GamePreviews/Corrobot1.png';
import Corrobot2 from '@images/GamePreviews/Corrobot2.png';
import Corrobot3 from '@images/GamePreviews/Corrobot3.png';
import Blood1 from '@images/GamePreviews/Blood1.png';
import Blood2 from '@images/GamePreviews/Blood2.png';
import Blood3 from '@images/GamePreviews/Blood3.png';
import Blood4 from '@images/GamePreviews/Blood4.png';
import Blood5 from '@images/GamePreviews/Blood5.png';
import Time1 from '@images/GamePreviews/Time1.png';
import Time2 from '@images/GamePreviews/Time2.png';
import Time3 from '@images/GamePreviews/Time3.png';
import Time4 from '@images/GamePreviews/Time4.png';
import Time5 from '@images/GamePreviews/Time5.png';
const games = [
{
title: 'Corrobot-rebounce',
description: 'A 3D sequel to Corrobot-Takeover',
jam: {
name: 'Nordic gamejam 2024',
url: 'https://itch.io/jam/nordic-game-jam-2024/rate/2659665'
},
viewUrl: 'https://botalex.itch.io/corrobot-rebounce',
images: [Corrobot1, Corrobot2, Corrobot3]
},
{
title: 'Unnamed blood game',
description: 'A game based on an unique kind of combat',
jam: {
name: 'Future Game Makers',
url: 'https://itch.io/jam/future-game-makers-jam-2024'
},
viewUrl: 'https://botalex.itch.io/mop-of-the-dead',
images: [Blood1, Blood2, Blood3, Blood4, Blood5]
},
{
title: 'One More Time',
description: 'What if time was money? A roguelike where you need to kill for time, which you can choose to spend.',
jam: {
name: 'Denmark Masters jam',
url: 'https://itch.io/jam/dmspiljam-november-2021'
},
viewUrl: 'https://botalex.itch.io/one-more-time',
images: [Time1, Time2, Time3, Time4, Time5]
}
];
</script>
<div class="grid grid-flow-row gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{#each games as game (game.title)}
<PostCard
title={game.title}
description={game.description}
jam={game.jam}
viewUrl={game.viewUrl}
images={game.images}
/>
{/each}
<!-- placeholder for upcoming game -->
<PostCard placeholder={true} />
</div>

View file

@ -0,0 +1,133 @@
<script>
import MobileTags from "./MobileTags.svelte";
import NameAndTag from "./NameAndTag.svelte";
export let isMobile = false;
export let name = "";
export let tags = ["null"];
export let isSnorre = false;
export let replaced = false;
// Shit code but who cares, if it works /shrug
</script>
<div
class="relative bg-grid-100 border-2 border-base-100 pl-1 pr-4 rounded-md cozette max-lg:pb-2"
>
{#if !isSnorre}
<div class=" developersProfile {isSnorre ? 'isSnorre' : ''} pl-1 font-mono">
<NameAndTag {name} {tags} {isMobile} />
<slot />
<MobileTags {tags} {isMobile} />
</div>
{:else}
<div class="w-full pl-1">
<div
class="developersProfile absolute snorre pl-4 font-mono pointer-events-none select-none"
>
<pre style="font-size: {!isMobile ? 1.5 : 1.5}rem;"></pre>
<span>
<pre></pre>
<pre></pre>
</span>
{#if isMobile}
<pre></pre>
{/if}
</div>
<div class="developersProfile snorre-overlay relative pl-1 font-mono">
<NameAndTag name="Snorre" {tags} {isMobile} />
<span>
<p>
I'm the diversity hire. <span
class="border-b"
style="border-image: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet); border-image-slice: 1;"
>(Gay)</span
>
</p>
<!-- <p><a href="https://www.linkedin.com/in/snorrealtschul/" target="_blank" style="color:lightblue;">My website</a></p> -->
<p>
<a
href="https://spoodythe.one/"
target="_blank"
style="color:lightblue;">My website</a
>
</p>
</span>
<MobileTags {tags} {isMobile} />
</div>
</div>
{/if}
<div
class="{replaced
? ''
: 'hidden'} replaced flex justify-center items-center h-full absolute top-0 left-0 down-0 right-0"
>
<div class="corner-border-container px-2">Replaced by AI¹</div>
</div>
</div>
<style>
.replaced {
background: repeating-linear-gradient(
45deg,
#0009,
#0009 15px,
#000a 15px 20px
);
}
.corner-border-container {
--length: 5px;
--width: 1px;
--line-color: #eeeeee;
background-color: #0008;
background-image:
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color)),
linear-gradient(var(--line-color), var(--line-color));
background-size:
var(--length) var(--width),
var(--width) var(--length),
var(--length) var(--width),
var(--width) var(--length),
var(--length) var(--width),
var(--width) var(--length),
var(--length) var(--width),
var(--width) var(--length);
background-position:
top left,
top left,
top right,
top right,
bottom right,
bottom right,
bottom left,
bottom left;
background-repeat: no-repeat;
}
.developersProfile:not(.snorre):not(.snorre-overlay) {
/* background-image: linear-gradient(var(--color-neutral) 33%, rgba(255,255,255,0) 0%); */
/* background-image: linear-gradient(var(--color-neutral) 100%);
background-position: left;
background-size: 0.1rem 0.5rem;
background-repeat: repeat-y; */
}
.snorre {
/* border-left: dashed transparent 0.1rem;
border-image: linear-gradient(to bottom, red, orange, yellow, green, blue, indigo, violet);
border-image-slice: 1; */
}
.snorre-overlay {
/* background-image: linear-gradient(rgba(255,255,255,0) 0%, rgba(255,255,255,0) 40%, var(--color-base-200) 40%); */
/* background-position: left;
background-size: 0.1rem 0.5rem;
background-repeat: repeat-y; */
}
</style>

View file

@ -0,0 +1,8 @@
<div class="profileSpacer"></div>
<style>
.profileSpacer{
height: 1px;
border-bottom: solid oklch(var(--b3));
}
</style>

View file

@ -0,0 +1,178 @@
<script lang="ts">
import onMount from "@e/onMount";
import { Vector2 } from "../zhen/Utils/Vector2";
// Params
let mouseMoveScale: number = 0.25;
let targetTextLenght: number = 100;
// Site variables
let mousePos: Vector2;
// Element binded variables
let mouseRelativeScaled: Vector2 = new Vector2(0, 0);
let windowWidth = 0;
let windowHeight = 0;
let screenCenter: Vector2;
let StartPageAnimated: Element | null;
let windowRef: Window;
function onMouseMoved(event: MouseEvent) {
mousePos = new Vector2(event.clientX, event.clientY);
updateAnimation(mousePos);
}
function updateAnimation(mousePos: Vector2) {
let mouseRelativePos = mousePos.Sub(screenCenter);
mouseRelativeScaled = mouseRelativePos.Scale(mouseMoveScale);
//console.log(mouseRelativePos.x+"\n"+mouseRelativePos.y);
}
onMount(() => {
windowRef = window;
const updateDimensions = () => {
windowWidth = windowRef.innerWidth;
windowHeight = windowRef.innerHeight;
screenCenter = new Vector2(windowWidth / 2, windowHeight / 2);
//console.log("Window size changed: (" + windowWidth + ", " + windowHeight + ")");
};
updateDimensions(); // On first pass
windowRef.addEventListener("resize", updateDimensions);
const RevertToOrigin = () => {
if (
navigator.userAgent.search(/gecko/i) > 0 &&
StartPageAnimated !== null
) {
StartPageAnimated.classList.add("FirefoxSmoothTranition");
}
updateAnimation(new Vector2(windowWidth / 2, windowHeight / 2));
};
document.documentElement.addEventListener("mouseleave", RevertToOrigin);
const RemoveFirefoxSmoothTranition = () => {
if (
navigator.userAgent.search(/gecko/i) > 0 &&
StartPageAnimated !== null
) {
StartPageAnimated.classList.remove("FirefoxSmoothTranition");
}
};
document.documentElement.addEventListener(
"mouseenter",
RemoveFirefoxSmoothTranition,
);
return () => {
windowRef.removeEventListener("resize", updateDimensions);
};
});
const programmingLanguages: string[] = [
"C++",
"C#",
"ARDUINO",
"PYTHON",
"JAVA",
"JAVASCRIPT",
"TYPESCRIPT",
"HTML",
"CSS",
];
function getRandomInt(max: number) {
return Math.floor(Math.random() * max);
}
function GrabRandomString() {
let outString: string = "";
while (outString.length < targetTextLenght) {
outString +=
programmingLanguages[
getRandomInt(programmingLanguages.length)
] + " ";
}
return outString; // At about target size
}
</script>
<svelte:window on:mousemove={onMouseMoved} />
<div
class="StartPageAnimated top-0 left-0 w-full h-full"
id="StartPageAnimated"
bind:this={StartPageAnimated}
style="transform: translate({mouseRelativeScaled.x}px, {mouseRelativeScaled.y}px) translateZ(0) rotate(0.001deg);"
>
{#each {length: 100} as _, i}
<span
class="rotate45 SkillsText"
>
{GrabRandomString()}
</span
>
{/each}
</div>
<style>
.StartPageContainer {
/* height: 40vh; */
background-color: burlywood;
overflow: hidden;
position: relative;
justify-content: center;
align-items: center;
display: flex;
padding: 0;
}
.StartPageAnimated {
padding: 0;
transition: transform 1000ms cubic-bezier(0.16, 1.63, 0.01, 0.99);
-moz-transition: none;
justify-content: center;
vertical-align: middle;
display: flex;
pointer-events: none;
}
.FirefoxSmoothTranition {
transition: transform 1000ms cubic-bezier(0.16, 1.63, 0.01, 0.99);
-moz-transition: transform 1000ms cubic-bezier(0.16, 1.63, 0.01, 0.99) !important;
}
.SkillsText {
font-family: "CozetteVector";
text-align: start;
font-size: x-large;
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
width: 2rem;
color: rgb(66, 66, 66);
}
.rotate45 {
transform: rotate(-45deg); /* Rotate the element by 45 degrees */
}
</style>

View file

@ -0,0 +1,93 @@
<script lang="ts">
export let Tags = ["null"];
export let isMobile = false;
// Define an interface for our detailed color object.
interface ColorObject {
color1: string;
color2: string;
rotation: string;
offset: string;
}
// ColorType can be a simple string or a ColorObject.
type ColorType = string | ColorObject;
// Create an interface for the color mapping.
interface ColorsMapping {
[key: string]: ColorType;
}
// Define a class to manage the colors.
class ColorManager {
private colors: ColorsMapping;
constructor() {
this.colors = {
"programmer": "#0CC27F",
"uxdesigner": "#027893",
"3dartist": "#F4881C",
"2dartist": "#F1EAC0",
"2d/3dartist": { color1: "#F1EAC0", color2: "#F4881C", rotation: "-65deg", offset: "71.5%" },
"sound/story": { color1: "#F3EC2A", color2: "#EEC12A", rotation: "-65deg", offset: "50%" },
"sounddesigner": "#F3EC2A",
"storydesigner": "#EEC12A",
"back-endadmin": "#3236a8",
};
}
// Return the color for the given key or a default value.
getColor(key: string): ColorType {
return this.colors[key] || "#ccc";
}
}
// Create an instance of ColorManager.
const colorManager = new ColorManager();
</script>
<div class="flex gap-2" style="font-size: { !isMobile ? '0.875rem' : '2vw' };">
{#each Tags as tag}
{@const key = tag.replaceAll(" ", "").toLowerCase()}
{@const color = colorManager.getColor(key)}
{#if key.indexOf("/") < 0}
<!-- Single Color Badge -->
<div class="badge2" style="background-color: {typeof color === 'string' ? color : '#ccc'};">
<span class="invert">
{tag}
</span>
</div>
{:else}
<!-- Gradient Badge -->
{#if typeof color === 'object' && color !== null}
<div
class="badge2 cozette"
style="background: linear-gradient({color.rotation}, {color.color2} {color.offset}, {color.color1} {color.offset});">
<span class="invert">
{tag}
</span>
</div>
{:else}
<div class="badge2 cozette" style="background-color: #ccc;">
<span class="invert">
{tag}
</span>
</div>
{/if}
{/if}
{/each}
</div>
<style>
.badge2 {
display: inline-flex;
align-items: center;
justify-content: center;
height: 1.25rem; /* 20px */
line-height: 1.25rem; /* 20px */
width: fit-content;
padding-left: 0.563rem; /* 9.008px */
padding-right: 0.563rem; /* 9.008px */
border-radius: var(--rounded-badge, 1.9rem); /* 30.4px */
}
</style>

View file

@ -0,0 +1,175 @@
<script lang="ts">
import svelteLogo from "$lib/svelteLogos/svelte-logo.png";
import onMount from "@e/onMount";
import onDestroy from "@e/onDestroy";
import ArrowBigDown from "lucide-svelte/icons/arrow-big-down";
import fly from "@e/fly";
import re from "@ts/Redaction/Redactor";
const buildTime = __BUILD_TIME__;
let scrollY = 0;
const unscrollSpeed = 100;
let unscrollScrollDiv: HTMLDivElement;
let totalScroll = 0;
let unscrollInterval: number | undefined = undefined;
let lastScrollTime = 0; // Used to have delay before unscrolling
let isBeingTouched = false; // Phone support
const unscrollDelay = 100;
let isLeavingAnimating = false;
// prevent direct scroll
let notFirstScroll = false;
let tranisitionOverlay: HTMLElement;
// Function with more scroll control, by chatgpt
function smoothScrollTo(targetY: number, duration: number = 500): void {
const startY: number = window.scrollY;
const diff: number = targetY - startY;
if (duration <= 0) {
window.scrollTo(0, targetY);
return;
}
let startTime: number | null = null;
function step(timestamp: number): void {
if (startTime === null) startTime = timestamp;
const time = timestamp - startTime;
const percent = Math.min(time / duration, 1);
// easeInOutQuad easing
const ease =
percent < 0.5
? 2 * percent * percent
: -1 + (4 - 2 * percent) * percent;
window.scrollTo(0, startY + diff * ease);
if (time < duration) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
}
function onScroll() {
lastScrollTime = Date.now();
// console.log("scroll");
}
function onResize() {
totalScroll = document.documentElement.scrollHeight - window.innerHeight;
}
onDestroy(() => {
clearInterval(unscrollInterval);
});
export let hideOnPrint: boolean;
</script>
<svelte:window
bind:scrollY
on:scroll={() => {
onScroll();
}}
on:touchstart={() => {
isBeingTouched = true;
}}
on:touchend={() => {
isBeingTouched = false;
}}
on:resize={onResize}
/>
<div class="{hideOnPrint ? 'hide-on-print' : ''} w-full">
<div
class="hidden h-64 w-full flex flex-col justify-end items-center"
bind:this={unscrollScrollDiv}
>
<img
src="/images/memes/WhatDaDog.png"
class="w-32 h-32 object-contain"
alt="da dog"
/>
</div>
<!-- About footer -->
<div class="sticky bottom-0 flex flex-col justify-center pt-8 bg-base-300">
<div class="flex justify-center">
<div class="grid gap-8 sm:grid-cols-3 align-middle w-full">
<div class="flex flex-col items-center">
<span class="font-bold">© 2023-2025</span>
<br />
<span>{$re?.name ?? "BOT Alex"}</span>
<br />
<span>Benjamin Dreyer</span>
<br />
<span>Snorre Ettrup Altschul</span>
</div>
<div class="flex flex-col items-center">
<h3><b>Info</b></h3>
<!-- <a href="/" target="_blank">Recursion</a> -->
<div class="flex justify-center">
This website was made using <a
class="grid place-content-center"
target="_blank"
href="https://kit.svelte.dev/"
>
<img
class="pl-2"
src={svelteLogo}
style="height: 1.5rem;"
alt="SvelteKit logo"
/></a
>
</div>
<!-- <span -->
<!-- >Website <a -->
<!-- href="https://git.deprived.dev/DeprivedDevs/deprived-main-website" -->
<!-- target="_blank">source code</a -->
<!-- ></span -->
<!-- > -->
</div>
<div class="flex flex-col items-center">
<h3><b>Contact</b></h3>
<a href="mailto:{$re?.email ?? 'Alex@deprived.dev'}"
>{$re?.email ?? "alex@deprived.dev"}</a
>
<div class="mt-2"></div>
<!-- <a -->
<!-- href="https://discord.gg/awatEEqc3M" -->
<!-- target="_blank" -->
<!-- class="social" -->
<!-- > -->
<!-- <span>Discord</span> -->
<!-- <img -->
<!-- src="/images/icons/discord.svg" -->
<!-- class="w-8 h-8 object-contain" -->
<!-- alt="Discord" -->
<!-- /> -->
<!-- </a> -->
</div>
</div>
</div>
<div
class="flex w-full justify-center border-t border-base-100 border-dashed"
>
Last build: {buildTime} (+2 UTC)
</div>
</div>
</div>
<div
bind:this={tranisitionOverlay}
class="{isLeavingAnimating
? ''
: 'hidden'} fixed top-0 left-0 w-screen h-screen bg-base-200"
></div>
<!-- {#if isLeavingAnimating}
{/if} -->

450
src/routes/cv/+page.svelte Normal file
View file

@ -0,0 +1,450 @@
<script lang="ts">
import re from "@ts/Redaction/Redactor";
import * as m from "$paraglide/messages";
import { getLocale, setLocale, locales } from "$paraglide/runtime";
import SendHorizontal from "@lucide/svelte/icons/send-horizontal";
// Left side
import NameAndImage from "./comps/NameAndImage.svelte";
import ShortProfile from "./comps/ShortProfile.svelte";
import CombinedContacts from "./comps/CombinedContacts.svelte";
import LinkedInQR from "./comps/LinkedInQR.svelte";
// Right side
import Profile from "./comps/Profile.svelte";
import Experience from "./comps/Experience.svelte";
import Education from "./comps/Education.svelte";
import BiggestFlex from "./comps/BiggestFlex.svelte";
import TableOfProjects from "./comps/TableOfProjects.svelte";
// Decorations
import LeftTopDecor from "./comps/LeftTopDecor.svelte";
import BottomRightDecor from "./comps/BottomRightDecor.svelte";
import AlexWatermark from "./comps/AlexWatermark.svelte";
import RepeatedSkills from "./comps/RepeatedSkills.svelte";
// Discord embed
import preveiwImage from "$lib/alex/cv-comps/preview.png";
// pages
import Page2 from "./pages/page2.svelte";
// Print detection setup
import onMount from "@e/onMount";
import { goto } from "$app/navigation";
let debug = false;
onMount(() => {
const urlParams = new URLSearchParams(window.location.search);
let needsUrlUpdate = false;
// 1. Safely handle hideOnPrint without reloading
if (!urlParams.has("hideOnPrint")) {
urlParams.set("hideOnPrint", "1");
needsUrlUpdate = true;
}
// 2. Check for locale in URL, fallback to localStorage
const urlLocale = urlParams.get("locale");
const savedLocale = localStorage.getItem("cv_locale");
const targetLocale = urlLocale || savedLocale;
if (targetLocale && targetLocale !== getLocale()) {
setLocale(targetLocale as any); // Update Paraglide
if (!urlLocale) {
urlParams.set("locale", targetLocale);
needsUrlUpdate = true;
}
}
// 3. Update the URL silently if we changed anything
if (needsUrlUpdate) {
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
window.history.replaceState({}, "", newUrl);
}
const params = new URLSearchParams(window.location.search);
debug = params.has("debug");
loadMotivation();
});
function changeLanguage(tag: string) {
setLocale(tag as any);
localStorage.setItem("cv_locale", tag);
const url = new URL(window.location.href);
url.searchParams.set("locale", tag);
window.location.href = url.toString();
}
let motivationInput: HTMLTextAreaElement;
let motivation: string | null = "";
function loadMotivation() {
const content = localStorage.getItem("motivation");
if (content && motivationInput) {
motivationInput.value = content;
motivation = content;
}
}
function submitMotivation() {
if (!motivationInput) return;
const content = motivationInput.value;
if (!content.trim()) {
localStorage.removeItem("motivation");
} else {
localStorage.setItem("motivation", content);
}
window.location.reload();
}
function getFormattedDate(): string {
const date = new Date();
const day = String(date.getDate()).padStart(2, "0");
const month = String(date.getMonth() + 1).padStart(2, "0");
const year = date.getFullYear();
return `${day}-${month}-${year}`;
}
</script>
<div>
<title
>{motivation ? "CUSTOM" : ""}
{$re?.name ?? "Alex"}'s {getLocale().toUpperCase()} CV {getFormattedDate()}</title
>
<meta content="{$re?.name ?? 'Alex'}'s CV" property="og:title" />
<meta
content="This CV is made completely with svelte + html + css + js"
property="og:description"
/>
<meta content={preveiwImage} property="og:image" />
<meta content="#bdd6ee" data-react-helmet="true" name="theme-color" />
<div class="cv-info-container h-[4cm] flex flex-col w-full hide-on-print">
<div class="flex w-full h-full items-center justify-center gap-8">
<div class=" flex flex-col">
<div>
<div class="keyboard-key">P</div>
+
<div class="keyboard-key">CTRL</div>
+ Chrome = PDF
</div>
<div class="flex gap-2">
CV Languages:
{#each locales as tag}
<button
class="btn btn-xs {tag == getLocale()
? 'btn-filled btn-primary text-primary-content'
: 'btn-outline'}"
onclick={() => {
changeLanguage(tag);
// window.location.reload();
}}
>
{tag.toUpperCase()}
</button>
{/each}
</div>
</div>
<div class="flex flex-col">
<div>Insert motivation</div>
<form
onsubmit={(e) => {
e.preventDefault();
submitMotivation();
}}
class="join"
>
<textarea
bind:this={motivationInput}
class="join-item input-xs h-[3cm] w-[12cm] textarea textarea-outline resize-x"
placeholder="Type something..."
></textarea>
<button type="submit" class="btn btn-xs join-item">
<SendHorizontal />
</button>
</form>
</div>
</div>
</div>
<!-- space -->
<!-- space -->
<!-- space -->
<!-- space -->
<div class="w-full flex flex-col max-h-[70cm]">
<div
class="relative w-full flex justify-center {$re?.name ? 'hidden' : ''}"
>
<RepeatedSkills
class="overcozette-force text-5xl text-secondary/32 text-base-300 "
style="transform: translateY(-90rem)"
textOverride={["REDACTED_VERSION"]}
targetTextHeight={90}
targetTextWidth={150}
rotation="-25deg"
textRowPadding={"1rem"}
/>
</div>
<div
class="{$re?.name
? ''
: 'absolute'} h-full w-full flex md:justify-center items-center"
>
<div class="NotoSans flex flex-col cv-config include-in-print">
<div class="cv-container sections decorations">
<div id="left-section" class=" flex justify-center">
<img
class="absolute self-center top-0 bottom-0 text-white"
style="transform: rotate(-90deg) scale(550%) translate(-2.5mm, 0); background-color: rgba(1, 1, 1, 0.85)"
src="/images/Zhen/cv/ZRuler-F_Cu.svg"
alt=""
/>
<LeftTopDecor />
<BottomRightDecor Style="pointer-events: none;" />
<div
class="absolute rotate-12 width-[10cm] h-full right-[15cm]"
style="background-color: #bdd6ee"
></div>
<div class="text-[var(--left-text-color)] pointer-events-none">
<div
class="pointer-events-auto flex flex-col justify-center w-full items-center"
>
<NameAndImage />
<div class="py-2"></div>
<ShortProfile />
<div class="py-2"></div>
<CombinedContacts />
<div class="py-2"></div>
<LinkedInQR />
</div>
</div>
<div class="relative h-full flex flex-col justify-end items-center">
<div class="text-sm w-32 mr-32 opacity-90 text-slate-400">
<div class="bg-black opacity-75 rounded">
I designed this PCB<br />For the nRF52840
</div>
</div>
</div>
</div>
<div id="leftSectionSeperator"></div>
<div
id="right-section"
class="text-[var(--right-text-color)] bg-white"
>
<AlexWatermark Style="pointer-events: none;" />
<div id="TopRightSkillsText">
<RepeatedSkills
class="cozette-force"
targetTextHeight={30}
targetTextWidth={150}
/>
</div>
<div>
<Profile />
<BiggestFlex />
<TableOfProjects />
<Experience />
<Education />
</div>
</div>
</div>
<div class="flex justify-center hide-on-print py-4">
<div>===== Next page =====</div>
</div>
<Page2 />
</div>
</div>
</div>
</div>
<style lang="scss">
.cv-config * {
--left-text-color: #eeeeee;
--left-line-color: #999999;
--left-decor-text-color: #eeeeee;
--left-decor-line-color: #999999;
--qr-color: #999999;
--left-grid-line-color: #ffffff;
--left-grid-bg-color: #000000;
--left-grid-opacity: 0.1;
--left-grid-size: 10px;
--right-text-color: #333333;
}
.corner-border-container {
background-color: var(--left-grid-bg-color);
background-image:
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
),
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
),
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
),
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
);
background-size:
30px 4px,
/* top-left horizontal */ 4px 30px,
/* top-left vertical */ 30px 4px,
/* bottom-right horizontal */ 4px 30px; /* bottom-right vertical */
background-repeat: no-repeat;
background-position:
top left,
top left,
bottom right,
bottom right;
}
.bg-grid-cv {
background:
linear-gradient(
-90deg,
rgba(255, 255, 255, var(--left-grid-opacity)) 1px,
transparent 1px
),
linear-gradient(
rgba(255, 255, 255, var(--left-grid-opacity)) 1px,
transparent 1px
),
var(--left-grid-line-color);
background-size:
var(--left-grid-size) var(--left-grid-size),
var(--left-grid-size) var(--left-grid-size),
80px 80px,
80px 80px,
80px 80px,
80px 80px,
80px 80px,
80px 80px;
background-color: var(--color-base-100);
}
.cv-info-container {
background-color: #2b2a2a;
display: flex;
justify-content: center;
align-items: center;
.keyboard-key {
display: inline;
padding-left: 1mm;
padding-right: 1mm;
border-radius: 2mm;
background-color: #3e3d3d;
}
}
.cv-container {
width: 210mm;
height: 297mm;
background-color: #eeeeee;
overflow: visible;
display: flex;
padding: auto;
}
.sections {
// Shared between sections
> div {
display: grid;
z-index: 0;
// Needed to cuttoff the extra decoration
position: relative;
overflow: hidden;
}
#left-section {
// background-color: #bdd6ee;
> div:nth-child(5) {
z-index: 1;
width: 17.5rem;
left: 0;
padding-top: 30mm;
}
}
#right-section {
width: calc(100% / 3 * 2);
> div:last-child {
z-index: 1;
width: 100%;
left: 0;
display: grid;
place-items: center;
row-gap: 6mm;
padding-top: 45mm;
padding-bottom: 30mm;
// Disable interactivity for padding
// pointer-events: none;
}
}
}
.decorations {
#leftSectionSeperator {
position: relative;
height: 100%;
width: 0%;
z-index: 1;
overflow: visible;
> div {
position: absolute;
height: 100%;
width: 5mm;
z-index: 1;
background: linear-gradient(90deg, #3636364f, #00000000);
}
}
> div {
#TopRightSkillsText {
position: absolute;
z-index: 0;
display: grid;
place-items: center;
vertical-align: top;
width: 100%;
place-content: center;
padding: 0;
height: 50mm;
mask-image: linear-gradient(180deg, #000 0%, transparent 110%);
color: rgb(190, 190, 190);
font-family: "CozetteVector";
font-size: x-large;
}
}
}
</style>

View file

@ -0,0 +1,22 @@
<script>
export let Style = "";
</script>
<div class="container" style={Style}>ALEX</div>
<style lang="scss">
.container {
position: absolute;
display: grid;
justify-self: end;
vertical-align: bottom;
align-self: flex-end;
// font settings
font-size: 80mm;
color: black;
opacity: 5%;
transform: translate(28%, -7.5mm) rotate(-90deg);
}
</style>

View file

@ -0,0 +1,44 @@
<script lang="ts">
import * as m from "$paraglide/messages";
import { onMount } from "svelte";
let motivation: string | null = "";
onMount(() => {
motivation = localStorage.getItem("motivation");
});
</script>
<div class="short-profile-container">
<div class="flex gap-1">
{#if motivation}
<b style="text-align:left;">Motivation</b>
{:else}
<b style="text-align:left;">{m["zhen.cv.flex.title"]()}</b>
<h1
style="font-size: 0.5rem; color: grey;"
class="flex flex-col justify-end"
>
{m["zhen.cv.flex.tooltip"]()}
</h1>
{/if}
</div>
<div class="text-[0.85rem] text-left">
{#if motivation}
<div>{motivation}</div>
{:else}
{@html m["zhen.cv.flex.body"]()}
{/if}
</div>
</div>
<style>
.short-profile-container {
width: 90%;
}
.short-profile-container > div:first-child {
width: 100%;
/* Bottom border stripe*/
border-bottom: 1mm solid black;
}
</style>

View file

@ -0,0 +1,53 @@
<script>
export let Style = "";
</script>
<div class="container" style={Style}>
<div class="w-full flex justify-center">
<div class="text-center"></div>
</div>
</div>
<style lang="scss">
.container {
position: absolute;
transform: translate(20.3mm, -5mm) rotate(-45deg);
display: grid;
justify-self: end;
vertical-align: bottom;
align-self: flex-end;
z-index: 0;
> div:nth-child(1) {
padding-top: 5mm;
//border-bottom: #4472c4 dashed 2mm;
background-image: linear-gradient(
to right,
var(--left-decor-line-color) 70%,
rgba(255, 255, 255, 0) 0%
);
background-position: top;
background-size: 6mm 1.5mm;
background-repeat: repeat-x;
}
> div:nth-child(2) {
background-color: var(--left-decor-line-color);
width: 100mm;
height: 25mm;
// Text
display: grid;
place-content: center;
align-content: flex-start;
> div {
padding-top: 3.5mm;
color: #4a7bcf;
font-weight: bold;
}
}
}
</style>

View file

@ -0,0 +1,7 @@
<script>
import Contact from "./Contact.svelte";
import OtherContact from "./OtherContact.svelte";
</script>
<Contact/>
<OtherContact/>

View file

@ -0,0 +1,81 @@
<script lang="ts">
import re from "@ts/Redaction/Redactor";
</script>
<div class="container">
<div>
<b style="text-align:left;"> Contact </b>
</div>
<div class="table-display">
<div class="table-item">
<div>Email</div>
<div>{$re?.email ?? "alex@deprived.dev"}</div>
</div>
<div class="table-item">
<div>Phone</div>
<div>{$re?.phone ?? "1-800-273-8255"}</div>
</div>
<div class="table-item">
<div>LinkedIn</div>
<a
href={$re?.linkedIn.link ??
"https://www.youtube.com/watch?v=PaPotS8GSpc"}
>{$re?.linkedIn.text ?? "cool video lmao"}</a
>
</div>
</div>
</div>
<style lang="scss">
.container {
display: grid;
place-items: center;
width: 70%;
}
.container > div:first-child {
width: 100%;
/* Bottom border stripe*/
border-bottom: 1mm solid var(--left-line-color);
}
.table-display {
width: 100%;
}
.table-item {
display: flex;
justify-items: start;
width: 100%;
border-bottom: 0.25mm solid #000000;
> a {
text-decoration: underline;
}
> div,
> a {
&:first-child {
width: 35%;
font-size: 4mm;
display: grid;
place-content: center start;
border-right: rgba(128, 128, 128, 0.4) dashed 0.1mm;
}
&:nth-child(2) {
width: 65%;
font-size: 3.25mm;
display: grid;
place-content: center;
padding-left: 1mm;
}
}
}
</style>

View file

@ -0,0 +1,53 @@
<script>
import SasLogo from "$lib/alex/cv-comps/SASLogo.png";
import IconAndText2 from "./IconAndText2.svelte";
import re from "@src/ts/Redaction/Redactor";
import env, { initEnv } from "@src/ts/EnvHandler";
import onMount from "@src/optimizers/onMount";
onMount(() => {
initEnv();
});
</script>
<div class="container h-10">
<div>
<b style="text-align:left;"> Education </b>
</div>
<div class="flex justify-center p-2 w-full">
<IconAndText2
logo={$re?.education[0].imageId.replace("[PB]", env.POCKETBASE_URL) ?? ""}
>
<b>{$re?.education[0].name ?? "University 🤮"}</b><br />
<p style="font-size: 0.5rem;">AI and data</p>
</IconAndText2>
<IconAndText2 logo={$re?.education[1].imageId ?? ""}>
<b>{$re?.education[1].name ?? "High School 🤮"}</b><br />
<p style="font-size: 0.5rem;">Computer science</p>
</IconAndText2>
<IconAndText2 logo={SasLogo}>
<b>Master class</b><br />
<p style="font-size: 0.5rem;">SAS Programming</p>
</IconAndText2>
<IconAndText2 logo={$re?.education[2].imageId ?? ""}>
<span class="font-semibold"
>{$re?.education[2].name ?? "Paid vecation/certificate"}</span
><br />
<p style="font-size: 0.5rem;">VR development</p>
</IconAndText2>
</div>
</div>
<style lang="scss">
.container {
display: grid;
place-items: center;
width: 90%;
> div:first-child {
border-bottom: black 1mm solid;
width: 100%;
}
}
</style>

View file

@ -0,0 +1,74 @@
<script>
import re from "@src/ts/Redaction/Redactor";
import IconAndText from "./IconAndText.svelte";
</script>
<div class="container">
<div>
<b style="text-align:left;"> Experience </b>
</div>
<div class="table">
<div class="table-item">
<IconAndText logo={$re?.experience[0].imageId ?? ""}>
<b>Full-stack</b> - Part-time<br />
{$re?.experience[0].name ?? "[REDACTED] Deprived devs"}
<br />
<i>Feb 2025 - Now</i>
</IconAndText>
</div>
<div class="table-item">
<IconAndText logo={$re?.experience[1].imageId ?? ""}>
<b>Data annotator</b> - Free-time<br />
{$re?.experience[1].name ?? "Some AI company"}<br />
<i>Jul 2024 - Now</i>
</IconAndText>
</div>
<div class="table-item">
<IconAndText logo={$re?.experience[2].imageId ?? ""}>
<b>3D printer manager</b> - Volunteer<br />
{$re?.experience[2].name ?? "Actually Volunteering"}<br />
<i>Nov 2023 - Jan 2026</i>
</IconAndText>
</div>
<div class="table-item">
<IconAndText logo={$re?.experience[3].imageId ?? ""}>
<b>Machine Learning Engineer</b> - Short term intern<br />
{$re?.experience[3].name ?? "YKYK"}<br />
<i>Apr 2024 - Apr 2024</i>
</IconAndText>
</div>
<div class="table-item">
<IconAndText logo={$re?.experience[4].imageId ?? ""}>
<b>Assistant</b> - Short term intern<br />
{$re?.experience[4].name ??
"Awesome VR place, but got rejected 2 times after"}<br />
<i>Oct 2020 - Oct 2020</i>
</IconAndText>
</div>
</div>
</div>
<style lang="scss">
.container {
display: grid;
place-items: center;
width: 90%;
overflow: hidden;
& > div:first-child {
width: 100%;
/* Bottom border stripe*/
border-bottom: 1mm solid black;
}
}
.table-item {
padding: 2mm;
&:not(:last-child) {
border-bottom: 0.25mm solid #000000;
}
}
</style>

View file

@ -0,0 +1,165 @@
<script lang="ts">
import QrCode from "svelte-qrcode";
import DeprivedLogo from "$lib/images/DeprivedLogo.svelte";
const cols = 9;
const rows = 7;
// Geometry Constants
const width = 73; // px
const height = width * 1.1547; // Perfect hexagonal ratio
const gap = 4; // Space between hexagons
// Calculations for layout
const horizontalSpacing = width + gap;
const verticalOverlap = height * 0.25;
const rowHeight = height - verticalOverlap + gap;
const offset = horizontalSpacing / 2;
const hexPath =
"polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)";
import { getSkills, Skill } from "@src/ts/misc/ZhenSkills";
import * as m from "$paraglide/messages";
import { getLocale, setLocale, locales } from "$paraglide/runtime";
import re from "@ts/Redaction/Redactor";
import { redirect } from "@sveltejs/kit";
let cvLink: string = "";
$: cvLink = `/cv?locale=${getLocale() == "en" ? "dk" : "en"}&key=${$re?.key ?? "nah"}`;
let skills: Skill[] = [];
$: skills = getSkills(cvLink);
function getIndex(r: int, c: int): int {
return Math.floor(r / 2) * (2 * cols + 1) + (r % 2) * (cols + 1) + c;
}
</script>
<div class=" text-[#121212] flex flex-col pb-auto">
<div class="flex justify-between items-center px-8 py-4">
<div class="font-semibold text-2xl">
{m["zhen.cv.page2.mini-projects.title"]()}
</div>
<div class="flex gap-4">
<div class="border-b-6 border-[#121212]/32">
{m["zhen.cv.page2.mini-projects.ask"]()}
</div>
<div class="border-b-6 border-[#7bd45d]">
{m["zhen.cv.page2.mini-projects.linked"]()}
</div>
</div>
</div>
<div class="relative overflow-show">
<div
class="relative pb-12"
style="padding-left: 13.5px; width: {cols * horizontalSpacing + offset}px"
>
{#each Array(rows) as _, r}
<div
class="flex"
style="
margin-bottom: -{verticalOverlap - gap}px;
padding-left: {r % 2 !== 0 ? offset : 0}px;
"
>
{#each Array(cols + (r % 2 == 0)) as _, c}
{@const x = getIndex(r, c)}
{#if x < skills.length}
<div class="hidden"></div>
<div
class=" {skills[x].link != undefined
? 'bg-[#7bd45d]'
: 'bg-[#121212]/32'} flex items-center justify-center shrink-0"
style="
width: {width}px;
height: {height}px;
clip-path: {hexPath};
margin-right: {gap}px;
"
>
{#if skills[x].link != undefined}
<a
href={skills[x].link}
target="_blank"
class="relative bg-[#eeeeee] w-full grid items-center shrink-0"
style="
width: {width * 0.9}px;
height: {height * 0.9}px;
clip-path: {hexPath};
"
>
<img class="p-3.5" src={skills[x].image} alt="" />
<div
class="w-0 h-0 absolute text-[0.2cm] top-0 pt-5 text-nowrap {skills[
x
].link != undefined
? 'text-[#589942]'
: 'text-[#121212]/50'}"
style="transform: translate(0, 0%) rotate(-30deg); margin-left: -0.2cm;"
>
{skills[x].alt}
</div>
</a>
{:else}
<div
class="relative bg-[#eeeeee] w-full grid items-center shrink-0"
style="
width: {width * 0.9}px;
height: {height * 0.9}px;
clip-path: {hexPath};
"
>
<img class="p-3.5" src={skills[x].image} alt="" />
<div
class="w-0 h-0 absolute text-[0.2cm] top-0 pt-5 text-nowrap {skills[
x
].link != undefined
? 'text-[#589942]'
: 'text-[#121212]/50'}"
style="transform: translate(0, 0%) rotate(-30deg); margin-left: -0.2cm;"
>
{skills[x].alt}
</div>
<!-- <div class="absolute top-0 right-0"> -->
<!-- <div -->
<!-- style="transform: translate(0, 0%) rotate(30deg); " -->
<!-- class="w-0 text-[0.2cm] w-full pt-1" -->
<!-- > -->
<!-- {skills[x].alt} -->
<!-- </div> -->
<!-- </div> -->
</div>
{/if}
</div>
{/if}
{/each}
</div>
{/each}
</div>
<div class="absolute pointer-events-none left-0 bottom-0">
<DeprivedLogo
Class="fill-[#121212]/6 px-4"
Style="width: 12.5cm; height: auto;"
/>
</div>
<div class="absolute right-0 bottom-0 px-4">
<div class="w-full flex justify-end">
<div class="flex flex-col justify-center items-center">
<div>{m["zhen.cv.page2.qrcode-text"]()}</div>
<div class="w-[50px] h-[50px] overflow-hidden">
<QrCode
size={205}
padding={0}
value={"https://deprived.dev" + cvLink}
background={"#eeeeee"}
/>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,44 @@
<script lang="ts">
export let logo: string;
export let logoWidths: string = "10%";
export let fontSize: string = "3mm";
export let lineHeight: string = "3.1mm";
import env, { initEnv } from "@src/ts/EnvHandler";
import onMount from "@e/onMount";
onMount(() => {
imageCaption = logo.split(/(\\|\/)/g).pop();
initEnv();
});
let imageCaption: undefined | string; // Not a high piority, you get the file name and thats it
</script>
<div class="container">
<img
src={logo.replace("[PB]", env.POCKETBASE_URL) ?? ""}
class="bg-white w-10 h-10 object-contain rounded shadow"
alt={imageCaption}
width={logoWidths}
/>
<div style="line-height: {lineHeight};">
<span style="font-size: {fontSize};">
<slot />
</span>
</div>
</div>
<style lang="scss">
.container {
display: flex;
justify-items: start;
& > div {
padding-left: 3mm;
text-align: start;
}
}
</style>

View file

@ -0,0 +1,29 @@
<script lang="ts">
export let logo: string;
export let logoWidths: string = "35%";
import onMount from "@e/onMount";
import { env, initEnv } from "@src/ts/EnvHandler";
onMount(() => {
imageCaption = logo.split(/(\\|\/)/g).pop();
initEnv();
});
let imageCaption: undefined | string; // Not a high piority, you get the file name and thats it
</script>
<div class=" h-full container flex">
<div class="flex h-full w-6 items-center overflow-hidden rounded">
<img
src={logo.replace("[PB]", env.POCKETBASE_URL) ?? ""}
class=" w-6 h-6 object-cover shadow"
alt={imageCaption}
width={logoWidths}
/>
</div>
<div class="px-1 ml-1 h-full text-[0.5rem]">
<span class="inline">
<slot />
</span>
</div>
</div>

View file

@ -0,0 +1,92 @@
<script>
import { getLocale, setLocale, locales } from "$paraglide/runtime";
import re from "@ts/Redaction/Redactor";
import RepeatedSkills from "./RepeatedSkills.svelte";
// Cedit
import LinkToSource from "./LinkToSource.svelte";
export let Style = "";
export let Class = "";
</script>
<div class="flex justify-end w-full">
<div class="container {Class}" style={Style}>
<div class="NotoSans-cn text-center bg-[var(--left-grid-bg-color)]">
<RepeatedSkills
rotation="0deg"
textOverride={["Hello", "你好", "Hej"]}
targetTextHeight={3}
targetTextWidth={50}
applyRotation={false}
/>
</div>
<!-- <div /> -->
<!-- <div class="flex bg-black justify-center"> -->
<!-- <div class="w-[6cm]"> -->
<!-- <LinkToSource /> -->
<!-- </div> -->
<!-- </div> -->
</div>
<div class="z-10 pr-4 pt-2 {!$re?.name ? 'hidden' : ''}">
<a
href="/cv?locale={getLocale() == 'en' ? 'dk' : 'en'}&key={$re?.key ??
'nah'}"
on:click|preventDefault={(e) =>
(window.location.href = e.currentTarget.href)}
><span class="text-blue-500 underline"
>{getLocale() == "en" ? "Se Dansk" : "View English"} CV</span
></a
>
</div>
</div>
<style lang="scss">
.container {
position: absolute;
transform: translate(-30mm, 7.5mm) rotate(-45deg);
display: grid;
justify-self: start;
vertical-align: top;
align-self: flex-start;
z-index: 0;
> div:nth-child(1) {
//background-color: #2f559622;
width: 100mm;
height: 17.5mm;
padding-bottom: 1mm;
padding-top: 1mm;
// Text inside
display: grid;
place-content: center;
border-bottom: var(--left-decor-line-color) dotted 1mm;
border-top: var(--left-decor-line-color) dotted 1mm;
&:first-child {
color: var(--left-decor-text-color);
font-size: 3mm;
//font-weight: bold;
}
}
> div:nth-child(2) {
padding-top: 4mm;
//border-bottom: #4472c4 dashed 2mm;
// background-color: var(--left-grid-bg-color);
background-image: linear-gradient(
to right,
var(--left-decor-line-color) 70%,
rgba(255, 255, 255, 0) 0%
);
background-position: bottom;
background-size: 6mm 1.5mm;
background-repeat: repeat-x;
}
}
</style>

View file

@ -0,0 +1,47 @@
<script lang="ts">
import svelteLogo from "$lib/svelteLogos/svelte-logo-cutout.svg";
import onMount from "@src/optimizers/onMount";
import re from "@src/ts/Redaction/Redactor";
</script>
<div class="container">
<div class="flex justify-center">
<div class="corner-border-container p-1 m-1">
<div class="flex">
This CV was made using html, css and <a
class="grid place-content-center"
href="https://kit.svelte.dev/"
><img src={svelteLogo} class="w-2 h-2" alt="SvelteKit logo" /></a
>
</div>
Sources:
<a
href={$re?.cv.sourceLink ??
"https://www.youtube.com/watch?v=0TaNezk4wNQ"}>CV source code</a
>
and
<a href="/cv?hideOnPrint=1">My Website</a>
</div>
</div>
</div>
<style lang="scss">
.container {
z-index: 1;
font-size: 0.5rem;
//white-space: nowrap;
* a {
padding-left: 1mm;
padding-right: 1mm;
text-decoration: underline;
}
div:nth-child(2) {
padding-bottom: 2mm;
}
}
</style>

View file

@ -0,0 +1,105 @@
<script lang="ts">
import re from "@src/ts/Redaction/Redactor";
// Gave up because a little drunk, so rest is chatgpt
function getHTML(url: string): Promise<string> {
return fetch(url, { method: "GET", headers: { Accept: "text/html" } }).then(
(res) => {
if (!res.ok)
throw new Error(`GET ${url} failed: ${res.status} ${res.statusText}`);
return res.text();
},
);
}
// recompute the promise when the (store-derived) URL changes
$: url = $re?.linkedIn?.imageId || "";
$: htmlPromise = url ? getHTML(url) : Promise.resolve("");
</script>
<div class="container">
<div>LinkedIn</div>
<div class="qrcode corner-border-container p-4">
{#await htmlPromise}
<span>Loading…</span>
{:then html}
{@html html}
{:catch err}
<span class="text-red-600">{err.message}</span>
{/await}
</div>
</div>
<style lang="scss">
.corner-border-container {
--length: 20px;
--width: 4px;
background-color: var(--left-grid-bg-color);
background-image:
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
),
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
),
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
),
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
),
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
),
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
),
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
),
linear-gradient(
var(--left-decor-line-color),
var(--left-decor-line-color)
);
background-size:
var(--length) var(--width),
var(--width) var(--length),
var(--length) var(--width),
var(--width) var(--length),
var(--length) var(--width),
var(--width) var(--length),
var(--length) var(--width),
var(--width) var(--length);
background-position:
top left,
top left,
top right,
top right,
bottom right,
bottom right,
bottom left,
bottom left;
background-repeat: no-repeat;
}
.qrcode {
transform: scale(0.9);
}
.container {
display: grid;
place-items: center;
& * {
font-size: 7.5mm;
}
}
</style>

View file

@ -0,0 +1,31 @@
<script lang="ts">
import re from "@ts/Redaction/Redactor";
import NamePlate from "./NamePlate.svelte";
import selfie from "$lib/alex/cv-comps/VRNerd.jpg";
import env from "@src/ts/EnvHandler";
</script>
<div class="nameAndImageContainer">
<NamePlate />
<div
class="mt-4 w-48 h-48 overflow-hidden shadow-xl rounded-lg flex justify-center items-center"
>
<img
src={$re?.selfie.replace("[PB]", env.POCKETBASE_URL) ?? selfie}
class="selfie-constraints object-cover"
alt="Selfie"
/>
</div>
</div>
<style>
.nameAndImageContainer {
display: grid;
place-items: center;
}
.selfie-constraints {
max-width: 100%;
}
</style>

View file

@ -0,0 +1,21 @@
<script lang="ts">
import re from "@ts/Redaction/Redactor";
</script>
<div class="name-plate-container">
<span style="text-align: center;">
<b>{$re?.name ?? "BOTAlex"}</b><br />
(He/Him)
</span>
</div>
<style>
.name-plate-container {
display: grid;
place-items: center;
width: 60%;
/* Bottom border stripe*/
border-bottom: 1mm solid var(--left-line-color);
}
</style>

View file

@ -0,0 +1,75 @@
<script lang="ts">
import re from "@ts/Redaction/Redactor";
</script>
<div class="container">
<div>
<b style="text-align:left;"> Other </b>
</div>
<div class="table-display">
<div class="table-item">
<div>Itch.io</div>
<a href={$re?.itch.link ?? "https://www.youtube.com/watch?v=PaPotS8GSpc"}
>{$re?.itch.text ?? "The same video"}</a
>
</div>
<div class="table-item">
<div>Github</div>
<a href="https://github.com/MagicBOTAlex">@MagicBOTAlex</a>
</div>
</div>
</div>
<style lang="scss">
.container {
display: grid;
place-items: center;
width: 70%;
}
.container > div:first-child {
width: 100%;
/* Bottom border stripe*/
border-bottom: 1mm solid var(--left-line-color);
}
.table-display {
width: 100%;
}
.table-item {
display: flex;
justify-items: start;
width: 100%;
border-bottom: 0.25mm solid #000000;
> a {
text-decoration: underline;
}
> div,
> a {
&:first-child {
width: 35%;
font-size: 4mm;
display: grid;
place-content: center start;
border-right: rgba(128, 128, 128, 0.4) dashed 0.1mm;
}
&:nth-child(2) {
width: 65%;
font-size: 3.25mm;
display: grid;
place-content: center;
padding-left: 1mm;
}
}
}
</style>

View file

@ -0,0 +1,30 @@
<script lang="ts">
import * as m from "$paraglide/messages";
</script>
<div class="short-profile-container">
<div class="flex gap-1 items-baseline">
<b style="text-align:left;">{m["zhen.cv.profile.title"]()}</b>
<div class="opacity-70 text-[0.5rem]">
{m["zhen.cv.profile.tooltip"]()}
</div>
</div>
<span>
{m["zhen.cv.profile.long"]()}
</span>
</div>
<style>
.short-profile-container {
display: grid;
place-items: center;
width: 90%;
}
.short-profile-container > div:first-child {
width: 100%;
/* Bottom border stripe*/
border-bottom: 1mm solid black;
}
</style>

View file

@ -0,0 +1,64 @@
<script lang="ts">
// Width of num chars and height nom of chars
export let targetTextWidth: number;
export let targetTextHeight: number;
export let textRowPadding: string = "";
export let rotation: string = "-45deg";
export let textOverride: string[] | undefined = undefined;
// Assign default value if textOverride is undefined
let repeatingText: string[] = textOverride ?? [
"C",
"C++",
"C#",
"ARDUINO",
"PYTHON",
"JAVA",
"JAVASCRIPT",
"TYPESCRIPT",
"HTML",
"CSS",
];
function getRandomInt(max: number) {
return Math.floor(Math.random() * max);
}
function GrabRandomString() {
let outString: string = "";
while (outString.length < targetTextWidth) {
outString += repeatingText[getRandomInt(repeatingText.length)] + " ";
}
return outString; // At about target size
}
</script>
<div {...$$restProps}>
{#each { length: targetTextHeight } as _, i}
<span
class=" SkillsText"
style="transform: rotate({rotation}); padding: {textRowPadding};"
>
{GrabRandomString()}
</span>
{/each}
</div>
<style>
.SkillsText {
text-align: start;
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
width: 2rem;
}
.rotate45 {
transform: rotate(-45deg);
}
</style>

View file

@ -0,0 +1,36 @@
<script lang="ts">
import re from "@ts/Redaction/Redactor";
import Circle from "lucide-svelte/icons/circle";
</script>
<div class="short-profile-container grid items-start text-sm">
<div>
<b style="text-align:left;"> Short profile </b>
</div>
<div class="p-0">
<Circle class="inline py-2" />Full-stack at {$re
?.shortProfileHiddenContent[0] ?? "Deprived devs"}
<br />
<Circle class="inline py-2" />Annotator at {$re
?.shortProfileHiddenContent[2] ?? "somewhere"}
<br />
<Circle class="inline py-2" />"AI and data" at {$re
?.shortProfileHiddenContent[1] ?? "some uni"}.
<br />
<Circle class="inline py-2" />Volunteer at {$re
?.shortProfileHiddenContent[3] ?? "Deprived devs"}.
</div>
</div>
<style>
.short-profile-container {
width: 70%;
}
.short-profile-container > div:first-child {
width: 100%;
/* Bottom border stripe*/
border-bottom: 1mm solid var(--left-line-color);
}
</style>

View file

@ -0,0 +1,227 @@
<script>
const baseClass =
"h-56 w-full bg-slate-400 flex text-white font-bold text-2xl";
import * as m from "$paraglide/messages";
import Expand from "@lucide/svelte/icons/expand";
</script>
<div class="bg-[#121212] relative w-full flex justify-between">
<div class="col-l w-full relative h-full flex flex-col gap-2 p-8">
<div class="text-2xl py-4">{m["zhen.cv.page2.title"]()}</div>
<div class={baseClass}>
<div class="w-full flex flex-col pr-[7cm]">
<div class="w-full text-[#121212] bg-[#f1f1f1] px-2">
Kubernetes Cluster
</div>
<div class="relative flex-1 w-full flex min-h-0">
<div class="w-full relative max-h-full flex flex-col">
<div class="w-full h-full flex flex-col">
<img
class="w-full h-full object-cover"
src="https://deprived.dev/assets/website/zhen/cv/fossflow-kubernetes.png"
alt=""
/>
</div>
</div>
<div class="absolute left-0 bottom-0">
<a
href="https://deprived.dev/assets/website/zhen/cv/fossflow-kubernetes.png"
target="_blank"
>
<div class="p-1">
<Expand />
</div>
</a>
</div>
<div
class="absolute kube-split bg-white/75 left-0 right-0 flex flex-col h-full"
>
<div class="pl-[245px] h-full w-full">
{#if true}
{@const rotation = "15deg"}
<div
class="h-full flex flex-col text-[#222222] text-xs py-7"
style="transform: rotate({rotation});"
>
<div style="transform: rotate(-{rotation});">
Multi-node cluster
</div>
<div style="transform: rotate(-{rotation});">
with vpn vlan
</div>
<div style="transform: rotate(-{rotation});">
connected through
</div>
<div style="transform: rotate(-{rotation});">wide-web by</div>
<div style="transform: rotate(-{rotation});">
Wireguard mesh
</div>
<div style="transform: rotate(-{rotation});">running in</div>
<div style="transform: rotate(-{rotation});">
in Qemu on NixOS
</div>
<div style="transform: rotate(-{rotation});"><br /></div>
<div style="transform: rotate(-{rotation});"><br /></div>
<div
style="transform: rotate(-{rotation});"
class="text-[0.6rem]"
>
Actual cluster diagram
</div>
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class={baseClass}>
<div class="w-full flex flex-col pr-[9cm]">
<div class="w-full text-[#121212] bg-[#f1f1f1] px-2">PCB Design</div>
<div class="relative flex-1 w-full flex min-h-0">
<div
class="w-full relative h-full flex flex-col justify-evenly pr-[45%]"
>
<img
class="w-full flex-1 object-cover min-h-0"
src="https://deprived.dev/assets/website/zhen/cv/kicad-pcb.png"
alt=""
/>
<img
class="w-full flex-1 object-cover min-h-0"
src="https://deprived.dev/assets/website/zhen/cv/soldering-stack.jpeg"
alt=""
/>
</div>
<div class="absolute quad-split left-0 right-0 flex flex-col h-full">
<img
class="w-full h-full object-fit"
src="https://deprived.dev/assets/website/zhen/cv/pnp.png"
alt=""
/>
</div>
</div>
</div>
</div>
</div>
<div
class="col-border absolute bg-[#121212] left-0 right-0 h-full flex flex-col gap-2 p-8"
></div>
<div
class="col-border-2 absolute left-0 right-0 h-full flex flex-col gap-2 p-8"
></div>
<div class="col-r absolute left-0 right-0 h-full flex flex-col gap-2 p-8">
<div class="text-2xl p-4"><br /></div>
<div class="{baseClass} ">
<div class="w-full flex flex-col pl-[9.5cm]">
<div class="w-full text-[#121212] bg-[#f1f1f1] text-end px-2">
Frontend
</div>
<img
class="w-full h-full object-cover object-top-left"
src="https://deprived.dev/assets/website/zhen/cv/cv-inception.png"
alt=""
/>
</div>
</div>
<div class="{baseClass} ">
<div class="w-full flex flex-col pl-[7.5cm]">
<div class="w-full text-[#121212] bg-[#f1f1f1] text-end px-2">
Embedded
</div>
<div class="relative flex-1 w-full flex min-h-0">
<div class="w-full relative h-full flex flex-col">
<img
class="w-full h-full object-cover pr-25"
src="https://deprived.dev/assets/website/zhen/cv/countrProto.jpeg"
alt=""
/>
</div>
<div
class="absolute split-embedded left-0 right-0 flex flex-col h-full"
>
<img
class="w-full h-full pl-45 object-cover"
src="https://deprived.dev/assets/website/zhen/cv/selfie.jpeg"
alt=""
/>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
div {
/* Counts from left side*/
--top: 60%;
--bot: calc(100% - var(--top));
}
.col-r {
clip-path: polygon(var(--top) 0, 100% 0, 100% 100%, var(--bot) 100%);
}
.quad-split {
--ratio: 57.5%;
--offset: -7.5%;
clip-path: polygon(
calc(var(--ratio) + var(--offset)) 0,
100% 0,
100% 100%,
calc(100% - var(--ratio) + var(--offset)) 100%
);
/* clip-path: polygon(50% 0, 100% 0, 100% 100%, 50% 100%); */
}
.split-frontend {
--ratio: 57.5%;
--offset: 7.5%;
clip-path: polygon(
calc(var(--ratio) + var(--offset)) 0,
100% 0,
100% 100%,
calc(100% - var(--ratio) + var(--offset)) 100%
);
/* clip-path: polygon(50% 0, 100% 0, 100% 100%, 50% 100%); */
}
.split-embedded {
--ratio: 56%;
--offset: 0%;
clip-path: polygon(
calc(var(--ratio) + var(--offset)) 0,
100% 0,
100% 100%,
calc(100% - var(--ratio) + var(--offset)) 100%
);
/* clip-path: polygon(50% 0, 100% 0, 100% 100%, 50% 100%); */
}
.kube-split {
--ratio: 56%;
--offset: 1%;
clip-path: polygon(
calc(var(--ratio) + var(--offset)) 0,
100% 0,
100% 100%,
calc(100% - var(--ratio) + var(--offset)) 100%
);
}
.col-border {
--border-size: 16px;
clip-path: polygon(
calc(var(--top) - var(--border-size)) 0,
100% 0,
100% 100%,
calc(var(--bot) - var(--border-size)) 100%
);
}
.col-border-2 {
--border-size: 0px;
clip-path: polygon(
calc(var(--top) - var(--border-size)) 0,
100% 0,
100% 100%,
calc(var(--bot) - var(--border-size)) 100%
);
}
</style>

View file

@ -0,0 +1,92 @@
<script lang="ts">
import * as m from "$paraglide/messages";
</script>
<div class="container flex flex-col items-center">
<div class="flex gap-1">
<b style="text-align:left;">
{m["zhen.cv.projects.title"]()}
</b>
<span class="opacity-70 text-[0.5rem] flex justify-end flex-col">
{m["zhen.cv.projects.tooltip"]()}
</span>
</div>
<div class="table-display text-sm justify-center">
<div class="table-item">
<div>HTML</div>
<div>{m["zhen.cv.projects.cv"]()}</div>
</div>
<div class="table-item">
<div>Computer vision</div>
<div>{m["zhen.cv.projects.CV"]()}</div>
</div>
<div class="table-item">
<div>Arduino/embedded</div>
<div>{m["zhen.cv.projects.embedded"]()}</div>
</div>
<!-- <div class="table-item"> -->
<!-- <div>App dev</div> -->
<!-- <div>Made an Doulingo'ish app for learning chinese.</div> -->
<!-- </div> -->
<div class="table-item">
<div>Open-source</div>
<div>{m["zhen.cv.projects.open"]()}</div>
</div>
<div class="table-item">
<div>PCB design</div>
<div>{m["zhen.cv.projects.pcb"]()}</div>
</div>
<div class="table-item">
<div>Kubernetes</div>
<div>{m["zhen.cv.projects.kube"]()}</div>
</div>
</div>
</div>
<style lang="scss">
.container > div:first-child {
width: 90%;
/* Bottom border stripe*/
border-bottom: 1mm solid black;
white-space: pre;
}
.table-display {
width: 90%;
}
.table-item {
display: flex;
justify-items: start;
width: 100%;
border-bottom: 0.25mm solid #000000;
> a {
text-decoration: underline;
}
> div,
> a {
color: #000000;
&:first-child {
width: 25%;
font-size: 3mm;
display: grid;
place-content: center start;
font-weight: bold;
border-right: rgba(128, 128, 128, 0.4) dashed 0.1mm;
}
&:nth-child(2) {
font-size: 3.5mm;
padding-left: 2mm;
}
}
}
</style>

View file

@ -0,0 +1,52 @@
<script lang="ts">
import HexagonSkills from "../comps/HexagonSkills.svelte";
import SlantedProjectHighlights from "../comps/SlantedProjectHighlights.svelte";
</script>
<div class="A4 include-in-print">
<SlantedProjectHighlights />
<div class="flex flex-col">
{#each { length: 16 } as _, i}
<div
class="warning-tape w-full"
style="height: 1px; --offset: {i * (40 - 1)}px"
></div>
{/each}
</div>
<HexagonSkills />
</div>
<style>
.A4 {
width: 210mm;
height: 296mm;
background-color: #eeeeee;
}
.warning-tape {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
/* Variables */
--color-first: #eeeeee;
--color-second: #121212;
background: linear-gradient(
-45deg,
var(--color-first) 25%,
var(--color-second) 25%,
var(--color-second) 50%,
var(--color-first) 50%,
var(--color-first) 75%,
var(--color-second) 75%,
var(--color-second)
);
background-size: 40px 40px;
/* Apply the offset here */
background-position: calc(var(--offset)) 0;
}
</style>

View file

@ -0,0 +1,5 @@
<script>
import Shop from "@shop/_shop_main.svelte";
</script>
<Shop/>

View file

@ -0,0 +1,5 @@
<script>
import Art_test from "@shop/_art_test.svelte";
</script>
<Art_test />

View file

@ -0,0 +1,5 @@
<script>
import Trackers from "@shop/_trackers.svelte";
</script>
<Trackers />

View file

@ -0,0 +1,6 @@
<script>
import Trackers from "@shop/_trackers.svelte";
</script>
<Trackers/>

View file

@ -0,0 +1,86 @@
<script lang="ts">
import ProjectEntry from "./lib/ProjectEntry.svelte";
</script>
<div class="content">
<h1>Benjamin's portfolie for Informatik</h1>
<p>
Forneden kan ses en række projekter som er blevet lavet i Informatik C og efterfølgende Informatik B.
</p>
<div class="projects">
<ProjectEntry
thumbnail_url={"/portfolios/sveske/appLab/thumb.png"}
thumbnail_alt={"App Lab"}
download={"/portfolios/sveske/appLab/applab_rapport.pdf"}
title={"1.G - App Lab: Idle spil i browseren"}
summary={"Vi udviklede et idle spil i App Lab. Brugte JavaScript til at programmere spillet."}
/>
<ProjectEntry
thumbnail_url={"/portfolios/sveske/teachable_machine/thumb.png"}
thumbnail_alt={"Teachable Machine"}
download={"/portfolios/sveske/teachable_machine/teachable_machine_rapport.pdf"}
title={"1.G - Teachable Machine: Håndtegns detektor"}
summary={"I dette projekt udarbejdede vi en teachable machine model til at detektere forskellige håndtegn"}
/>
<ProjectEntry
thumbnail_url={"/portfolios/sveske/firebase/thumb.png"}
thumbnail_alt={"Firebase"}
download={"/portfolios/sveske/firebase/firebase_rapport.pdf"}
title={"1.G - Firebase: Netværk scoreboard i Unity"}
summary={"Med brug af Firebase udviklede vi et scoreboard som synkroniserer med en Firebase Data Store. Scoreboardet blev implementeret i et tidligere spil udviklet i Teknologi B"}
/>
<ProjectEntry
thumbnail_url={"/portfolios/sveske/the_red_paper/thumb.png"}
thumbnail_alt={"The Red Paper"}
download={"/portfolios/sveske/the_red_paper/the_red_paper_rapport.pdf"}
title={"2.G - HTML: Hjemmeside udviklet ud fra gestalt lovene"}
summary={"Udviklede en klon af \"Den Blå Avis\", som forsøger at demonstrere en række af de forskellige gestaltlove"}
/>
<ProjectEntry
thumbnail_url={"/portfolios/sveske/makeymakey/thumb.png"}
thumbnail_alt={"MakyeMakey"}
download={"/portfolios/sveske/makeymakey/makeymakey_rapport.pdf"}
title={"2.G - MakeyMakey: Installation som spiller Minecraft lyde i en af skolens gange"}
summary={"Skulle lave en interresant installation til en lokation på skolen. Vi udviklede en sensor, som detekterede når en person gik ind på en af skolens gange med makeymakey'en og spillede grotte lyde fra Minecraft"}
/>
<ProjectEntry
thumbnail_url={"/portfolios/sveske/hmi/thumb.png"}
thumbnail_alt={"HMI"}
download={"/portfolios/sveske/hmi/hmi_rapport.pdf"}
title={"3.G - Human Machine Interface: Enhed som kan signalere brugeren hvis en person er på vej ind på brugerens værelse"}
summary={"Brugte M5 mikrokontrolleren til at forbinde en IR bevægelsessensor. Sensoren sender beskeder over netværket til et armbånd som er bundet om brugerens håndled"}
/>
<ProjectEntry
thumbnail_url={"/portfolios/sveske/prolog/thumb.png"}
thumbnail_alt={"Prolog"}
download={"/portfolios/sveske/prolog/prolog_rapport.pdf"}
title={"3.G - Prolog: Rejseekspertsystem"}
summary={"Brugte prolog programmeringsproget til at lave et ekspertsystem. Specifikt kan det give anbefalinger til rejsedestinationer ud fra en række faktorer som temperatur, økonomi, aktiviter osv."}
/>
<ProjectEntry
thumbnail_url={"/portfolios/sveske/eksamen/thumb.jpg"}
thumbnail_alt={"Informatik Eksamen"}
download={"/portfolios/sveske/eksamen/informatik_eksamen_rapport.pdf"}
title={"3.G - Eksamens projekt: Udvikling af opgavesystem til fysisk produkt om læringsdisplay af digital logik"}
summary={"Udviklede et opgavesystem i form af en app i Unity. Opgavesystemet virker i samarbejde med et fysisk produkt udviklet til eksamensprojektet i teknikfag - Digital Design og Udvikling A. Appen skal undervise computer science elever på gymnasiet om digital logik og logiske porte"}
/>
</div>
</div>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
}
.projects {
margin-top: 25px;
display: flex;
flex-direction: column;
gap: 25px;
max-width: 1000px;
}
</style>

View file

@ -0,0 +1,60 @@
<script lang="ts">
export let download : string = '404';
export let thumbnail_url : string = '/favicon.png';
export let thumbnail_alt : string = 'Picture describting the deprived devs logo';
export let title : string = '<title>';
export let summary : string = '<summary>';
</script>
<div class="news-card">
<a href={download}>
<div class="thumbnail">
<img src={thumbnail_url} alt={thumbnail_alt}/>
</div>
<div class="content">
<h3 id="title">{title}</h3>
<p id="summary-text">{summary}</p>
</div>
</a>
</div>
<style>
a {
text-decoration: none;
display: flex;
flex-direction: row;
gap: 15px;
}
.thumbnail > img {
object-fit: cover;
box-shadow: 5px 5px 10px 2px rgba(0, 0, 0, 0.5);
border-radius: 8px;
width: 150px;
height: auto;
}
.content {
flex-shrink: 2;
display: flex;
flex-direction: column;
gap: 10px;
}
#title {
margin: 0;
text-decoration: none;
color: var(--text2);
}
#summary-text {
margin: 0;
text-decoration: none;
color: var(--text3);
}
</style>

View file

@ -0,0 +1,5 @@
<div class="h-[594mm] w-[20mm] bg-white text-black">
{#each { length: 100 } as _, i}
<div>{i + 1}</div>
{/each}
</div>