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
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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue