git commit -am "Pruned git history"
All checks were successful
Rebuild signaller for deprived.dev to rebuild site / Rebuild Signaller (push) Successful in 21s
All checks were successful
Rebuild signaller for deprived.dev to rebuild site / Rebuild Signaller (push) Successful in 21s
This commit is contained in:
commit
158918c0f5
201 changed files with 24558 additions and 0 deletions
271
src/routes/+layout.svelte
Normal file
271
src/routes/+layout.svelte
Normal 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
1
src/routes/+layout.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const prerender = true;
|
||||
353
src/routes/+page.svelte
Normal file
353
src/routes/+page.svelte
Normal 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>
|
||||
133
src/routes/comps/Carousel.svelte
Normal file
133
src/routes/comps/Carousel.svelte
Normal 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}><</button>
|
||||
<button class="control" on:click={nextSlide}>></button>
|
||||
</div>
|
||||
|
||||
<div class="indicators">
|
||||
{#each images as _, index}
|
||||
<div
|
||||
class="indicator {index === currentIndex ? 'active' : ''}"
|
||||
on:click={() => (currentIndex = index)}
|
||||
></div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
464
src/routes/comps/CustomScrollBar.svelte
Normal file
464
src/routes/comps/CustomScrollBar.svelte
Normal 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 don’t 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>
|
||||
63
src/routes/comps/DeprivedTrackerSection.svelte
Normal file
63
src/routes/comps/DeprivedTrackerSection.svelte
Normal 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>
|
||||
16
src/routes/comps/FrontFold.svelte
Normal file
16
src/routes/comps/FrontFold.svelte
Normal 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>
|
||||
10
src/routes/comps/MobileTags.svelte
Normal file
10
src/routes/comps/MobileTags.svelte
Normal 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}
|
||||
15
src/routes/comps/NameAndTag.svelte
Normal file
15
src/routes/comps/NameAndTag.svelte
Normal 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>
|
||||
61
src/routes/comps/PostCard.svelte
Normal file
61
src/routes/comps/PostCard.svelte
Normal 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>
|
||||
|
||||
68
src/routes/comps/PostGrid.svelte
Normal file
68
src/routes/comps/PostGrid.svelte
Normal 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>
|
||||
|
||||
133
src/routes/comps/Profile.svelte
Normal file
133
src/routes/comps/Profile.svelte
Normal 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>
|
||||
8
src/routes/comps/ProfileSpacer.svelte
Normal file
8
src/routes/comps/ProfileSpacer.svelte
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<div class="profileSpacer"></div>
|
||||
|
||||
<style>
|
||||
.profileSpacer{
|
||||
height: 1px;
|
||||
border-bottom: solid oklch(var(--b3));
|
||||
}
|
||||
</style>
|
||||
178
src/routes/comps/SlantedText.svelte
Normal file
178
src/routes/comps/SlantedText.svelte
Normal 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>
|
||||
93
src/routes/comps/Tags.svelte
Normal file
93
src/routes/comps/Tags.svelte
Normal 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>
|
||||
175
src/routes/comps/Zooter.svelte
Normal file
175
src/routes/comps/Zooter.svelte
Normal 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
450
src/routes/cv/+page.svelte
Normal 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>
|
||||
22
src/routes/cv/comps/AlexWatermark.svelte
Normal file
22
src/routes/cv/comps/AlexWatermark.svelte
Normal 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>
|
||||
44
src/routes/cv/comps/BiggestFlex.svelte
Normal file
44
src/routes/cv/comps/BiggestFlex.svelte
Normal 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>
|
||||
53
src/routes/cv/comps/BottomRightDecor.svelte
Normal file
53
src/routes/cv/comps/BottomRightDecor.svelte
Normal 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>
|
||||
|
||||
7
src/routes/cv/comps/CombinedContacts.svelte
Normal file
7
src/routes/cv/comps/CombinedContacts.svelte
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
import Contact from "./Contact.svelte";
|
||||
import OtherContact from "./OtherContact.svelte";
|
||||
</script>
|
||||
|
||||
<Contact/>
|
||||
<OtherContact/>
|
||||
81
src/routes/cv/comps/Contact.svelte
Normal file
81
src/routes/cv/comps/Contact.svelte
Normal 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>
|
||||
53
src/routes/cv/comps/Education.svelte
Normal file
53
src/routes/cv/comps/Education.svelte
Normal 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>
|
||||
74
src/routes/cv/comps/Experience.svelte
Normal file
74
src/routes/cv/comps/Experience.svelte
Normal 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>
|
||||
165
src/routes/cv/comps/HexagonSkills.svelte
Normal file
165
src/routes/cv/comps/HexagonSkills.svelte
Normal 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>
|
||||
44
src/routes/cv/comps/IconAndText.svelte
Normal file
44
src/routes/cv/comps/IconAndText.svelte
Normal 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>
|
||||
29
src/routes/cv/comps/IconAndText2.svelte
Normal file
29
src/routes/cv/comps/IconAndText2.svelte
Normal 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>
|
||||
92
src/routes/cv/comps/LeftTopDecor.svelte
Normal file
92
src/routes/cv/comps/LeftTopDecor.svelte
Normal 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>
|
||||
47
src/routes/cv/comps/LinkToSource.svelte
Normal file
47
src/routes/cv/comps/LinkToSource.svelte
Normal 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>
|
||||
105
src/routes/cv/comps/LinkedInQR.svelte
Normal file
105
src/routes/cv/comps/LinkedInQR.svelte
Normal 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>
|
||||
31
src/routes/cv/comps/NameAndImage.svelte
Normal file
31
src/routes/cv/comps/NameAndImage.svelte
Normal 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>
|
||||
21
src/routes/cv/comps/NamePlate.svelte
Normal file
21
src/routes/cv/comps/NamePlate.svelte
Normal 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>
|
||||
75
src/routes/cv/comps/OtherContact.svelte
Normal file
75
src/routes/cv/comps/OtherContact.svelte
Normal 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>
|
||||
30
src/routes/cv/comps/Profile.svelte
Normal file
30
src/routes/cv/comps/Profile.svelte
Normal 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>
|
||||
64
src/routes/cv/comps/RepeatedSkills.svelte
Normal file
64
src/routes/cv/comps/RepeatedSkills.svelte
Normal 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>
|
||||
36
src/routes/cv/comps/ShortProfile.svelte
Normal file
36
src/routes/cv/comps/ShortProfile.svelte
Normal 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>
|
||||
227
src/routes/cv/comps/SlantedProjectHighlights.svelte
Normal file
227
src/routes/cv/comps/SlantedProjectHighlights.svelte
Normal 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>
|
||||
92
src/routes/cv/comps/TableOfProjects.svelte
Normal file
92
src/routes/cv/comps/TableOfProjects.svelte
Normal 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>
|
||||
52
src/routes/cv/pages/page2.svelte
Normal file
52
src/routes/cv/pages/page2.svelte
Normal 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>
|
||||
5
src/routes/shop/+page.svelte
Normal file
5
src/routes/shop/+page.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import Shop from "@shop/_shop_main.svelte";
|
||||
</script>
|
||||
|
||||
<Shop/>
|
||||
5
src/routes/shop/arthing/+page.svelte
Normal file
5
src/routes/shop/arthing/+page.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import Art_test from "@shop/_art_test.svelte";
|
||||
</script>
|
||||
|
||||
<Art_test />
|
||||
5
src/routes/shop/tracker/+page.svelte
Normal file
5
src/routes/shop/tracker/+page.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import Trackers from "@shop/_trackers.svelte";
|
||||
</script>
|
||||
|
||||
<Trackers />
|
||||
6
src/routes/shop/trackers/+page.svelte
Normal file
6
src/routes/shop/trackers/+page.svelte
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<script>
|
||||
import Trackers from "@shop/_trackers.svelte";
|
||||
</script>
|
||||
|
||||
<Trackers/>
|
||||
|
||||
86
src/routes/sveske/+page.svelte
Normal file
86
src/routes/sveske/+page.svelte
Normal 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>
|
||||
|
||||
60
src/routes/sveske/lib/ProjectEntry.svelte
Normal file
60
src/routes/sveske/lib/ProjectEntry.svelte
Normal 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>
|
||||
5
src/routes/test/+page.svelte
Normal file
5
src/routes/test/+page.svelte
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue