Compare commits
13 Commits
007b9ac3e4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba8bb0b023 | ||
|
|
badbfd7a74 | ||
|
|
8b40305b22 | ||
|
|
f6c44128b3 | ||
|
|
72137466dc | ||
|
|
ec2f7704e7 | ||
|
|
408afdfd1f | ||
|
|
9012debcfe | ||
|
|
d9163af01e | ||
|
|
f3f9caea70 | ||
|
|
322e4d0471 | ||
|
|
a9926fbbb7 | ||
|
|
a436da316d |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@ dist/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from 'astro/config';
|
||||
import icon from "astro-icon"
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({});
|
||||
export default defineConfig({
|
||||
integrations:[icon()],
|
||||
server: {
|
||||
allowedHosts: ["bag-valium-thee-distinct.trycloudflare.com"]
|
||||
}
|
||||
});
|
||||
|
||||
4735
package-lock.json
generated
4735
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,9 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^6.1.8"
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"astro": "^6.1.8",
|
||||
"astro-icon": "^1.1.5",
|
||||
"sweetalert2": "^11.26.24"
|
||||
}
|
||||
}
|
||||
BIN
public/CV.pdf
Normal file
BIN
public/CV.pdf
Normal file
Binary file not shown.
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1024" fill="none"><path fill="url(#a)" fill-rule="evenodd" d="M-217.58 475.75c91.82-72.02 225.52-29.38 341.2-44.74C240 415.56 372.33 315.14 466.77 384.9c102.9 76.02 44.74 246.76 90.31 366.31 29.83 78.24 90.48 136.14 129.48 210.23 57.92 109.99 169.67 208.23 155.9 331.77-13.52 121.26-103.42 264.33-224.23 281.37-141.96 20.03-232.72-220.96-374.06-196.99-151.7 25.73-172.68 330.24-325.85 315.72-128.6-12.2-110.9-230.73-128.15-358.76-12.16-90.14 65.87-176.25 44.1-264.57-26.42-107.2-167.12-163.46-176.72-273.45-10.15-116.29 33.01-248.75 124.87-320.79Z" clip-rule="evenodd" style="opacity:.154"/><path fill="url(#b)" fill-rule="evenodd" d="M1103.43 115.43c146.42-19.45 275.33-155.84 413.5-103.59 188.09 71.13 409 212.64 407.06 413.88-1.94 201.25-259.28 278.6-414.96 405.96-130 106.35-240.24 294.39-405.6 265.3-163.7-28.8-161.93-274.12-284.34-386.66-134.95-124.06-436-101.46-445.82-284.6-9.68-180.38 247.41-246.3 413.54-316.9 101.01-42.93 207.83 21.06 316.62 6.61Z" clip-rule="evenodd" style="opacity:.154"/><defs><linearGradient id="b" x1="373" x2="1995.44" y1="1100" y2="118.03" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient><linearGradient id="a" x1="107.37" x2="1130.66" y1="1993.35" y2="1026.31" gradientUnits="userSpaceOnUse"><stop stop-color="#3245FF"/><stop offset="1" stop-color="#BC52EE"/></linearGradient></defs></svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/hero.png
Normal file
BIN
src/assets/hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 266 KiB |
61
src/components/Box.astro
Normal file
61
src/components/Box.astro
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
interface Props {
|
||||
accent?: string;
|
||||
isTitle?: boolean
|
||||
}
|
||||
|
||||
const { accent, isTitle } = Astro.props as Props;
|
||||
|
||||
let opachity = 0.05;
|
||||
if (isTitle) {
|
||||
opachity = 0.2;
|
||||
}
|
||||
---
|
||||
|
||||
<div class="container">
|
||||
{accent && <div class="accent" style={`background-color: ${accent}; width:4px; border-radius: 999px; flex-shrink: 0;`}></div>}
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style define:vars={{color: `rgba(255,255,255,${opachity})`}}>
|
||||
.container {
|
||||
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
min-height: 120px;
|
||||
padding: 1rem;
|
||||
background: var(--color);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.18);
|
||||
color: white;
|
||||
transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.container:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 14px 28px rgba(0,0,0,0.24);
|
||||
border-color: rgba(255,255,255,0.14);
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
min-height: 100px;
|
||||
padding: 0.85rem;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
79
src/components/Button.astro
Normal file
79
src/components/Button.astro
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
href: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const { label, href, icon } = Astro.props;
|
||||
const navigationHandler = `window.open('${href}', '_blank')`;
|
||||
---
|
||||
|
||||
<div class="button" onclick={navigationHandler}>
|
||||
<Icon name={icon} class="icon" />
|
||||
<a rel="noreferrer">
|
||||
{label}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--transition-time: .28s;
|
||||
}
|
||||
|
||||
.button {
|
||||
min-width: 130px;
|
||||
width: min(13rem, 100%);
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: rgba(18, 18, 28, 0.24);
|
||||
backdrop-filter: blur(2px);
|
||||
box-shadow: 0 24px 50px rgba(0, 0, 0, 0.18);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 1.8rem;
|
||||
|
||||
cursor: pointer;
|
||||
transition: transform var(--transition-time), border-color var(--transition-time), background var(--transition-time), box-shadow var(--transition-time);
|
||||
gap: 0.75rem;
|
||||
padding: 0.95rem 1.2rem;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: rgba(32, 32, 46, 0.5);
|
||||
border-color: rgba(255, 255, 255, 0.22);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 24px 65px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
.button:hover a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.button a {
|
||||
text-decoration: none;
|
||||
color: #f5f2eb;
|
||||
text-align: left !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
flex-shrink: 0;
|
||||
color: #f5f2eb;
|
||||
}
|
||||
</style>
|
||||
169
src/components/Contact/ContactForm.astro
Normal file
169
src/components/Contact/ContactForm.astro
Normal file
@@ -0,0 +1,169 @@
|
||||
---
|
||||
const { title, description } = Astro.props;
|
||||
|
||||
import Box from "../Box.astro";
|
||||
---
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
|
||||
<Box>
|
||||
<form id="contactForm">
|
||||
<input type="text" name="_honey" id="honey" style="display:none;" tabindex="-1" autocomplete="off" />
|
||||
<input type="text" name="name" id="nameInput" class="name"placeholder="John Doe" autocomplete="name" required>
|
||||
<input type="email" name="email" id="emailInput" class="email" placeholder="john@example.com" autocomplete="email" required>
|
||||
<textarea name="message" id="messageInput" placeholder="Your message here" required></textarea>
|
||||
<input type="submit" value="Indienen" class="submitBtn">
|
||||
</form>
|
||||
</Box>
|
||||
|
||||
|
||||
<script>
|
||||
import Swal from 'sweetalert2';
|
||||
|
||||
const form = document.getElementById('contactForm') as HTMLFormElement | null;
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const nameInput = document.getElementById('nameInput') as HTMLInputElement | null;
|
||||
const emailInput = document.getElementById('emailInput') as HTMLInputElement | null;
|
||||
const messageInput = document.getElementById('messageInput') as HTMLTextAreaElement | null;
|
||||
const honeyInput = document.getElementById('honey') as HTMLInputElement | null;
|
||||
|
||||
if (!nameInput || !emailInput || !messageInput || !honeyInput) {
|
||||
console.error("One or more form fields were not found in the DOM.");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('name', nameInput.value);
|
||||
formData.append('email', emailInput.value);
|
||||
formData.append('message', messageInput.value);
|
||||
formData.append('_honey', honeyInput.value);
|
||||
|
||||
Swal.fire({
|
||||
title: 'Sending...',
|
||||
didOpen: () => Swal.showLoading()
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch('https://contact.herpiederpiee.nl/submit', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.status === 201) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Success!',
|
||||
text: 'Your message has been received!',
|
||||
});
|
||||
form.reset();
|
||||
} else if (response.status === 429) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Slow down!',
|
||||
text: result.detail,
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Oops...',
|
||||
text: result.detail || 'Something went wrong.',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Could not connect to the contact server.',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
input, textarea {
|
||||
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: rgba(18, 18, 28, 0.24);
|
||||
backdrop-filter: blur(2px);
|
||||
transition: all 200ms ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
font-size: 0.75rem;
|
||||
padding: 0.8rem 1rem;
|
||||
}
|
||||
|
||||
input::placeholder, textarea::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
background: rgba(18, 18, 28, 0.4);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 3px rgba(255, 255, 255, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
input:hover, textarea:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(18, 18, 28, 0.35);
|
||||
}
|
||||
|
||||
.submitBtn {
|
||||
background: linear-gradient(135deg, rgba(140, 90, 20, 0.4), rgba(96, 67, 8, 0.3));
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
padding: unset !important;
|
||||
transition: all 200ms ease;
|
||||
height: 3rem !important;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.submitBtn:hover {
|
||||
background: linear-gradient(135deg, rgba(160, 110, 30, 0.5), rgba(116, 87, 18, 0.4));
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.submitBtn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
|
||||
form textarea {
|
||||
height: 8rem;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 768px) {
|
||||
form {
|
||||
width: 100%;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.8rem 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
14
src/components/Contact/ContactImage.astro
Normal file
14
src/components/Contact/ContactImage.astro
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
import Box from "../Box.astro";
|
||||
---
|
||||
|
||||
<Box>
|
||||
<div class="image"></div>
|
||||
</Box>
|
||||
<style>
|
||||
.image {
|
||||
width: 40vw;
|
||||
height: 400px;
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
43
src/components/Contact/Page.astro
Normal file
43
src/components/Contact/Page.astro
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
import TitleCard from '../TitleCard.astro';
|
||||
import ContactForm from './ContactForm.astro';
|
||||
import ContactImage from './ContactImage.astro';
|
||||
import Section from '../Section.astro';
|
||||
|
||||
---
|
||||
|
||||
<Section className="contact-section" label="Contact">
|
||||
<TitleCard title="Contact" description="Neem gerust contact met me op!"/>
|
||||
<div class="formContaner">
|
||||
<ContactForm />
|
||||
<div class="imgContainer">
|
||||
<ContactImage />
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<style>
|
||||
.formContaner {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 40vw;
|
||||
height: 400px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Mobile: collapse to one column,
|
||||
so "Kennis" ends up fully under contact */
|
||||
@media (max-width: 768px) {
|
||||
.imgContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.formContaner {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
96
src/components/Experience/ExperienceBox.astro
Normal file
96
src/components/Experience/ExperienceBox.astro
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
const { experience } = Astro.props;
|
||||
import Box from "../Box.astro";
|
||||
const color: string = experience.color
|
||||
---
|
||||
|
||||
|
||||
|
||||
<Box accent={color}>
|
||||
<div class="titleRow">
|
||||
<div class="titleContainer">
|
||||
<h3 class="main-title">{experience.name}</h3>
|
||||
<p class="sub-title">{experience.role}</p>
|
||||
|
||||
<p class="description">
|
||||
{experience.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="dateContainer">
|
||||
<p>
|
||||
{experience.start.toLocaleDateString('en-US', { year: 'numeric', month: 'short' })}
|
||||
-
|
||||
{experience.end
|
||||
? experience.end.toLocaleDateString('en-US', { year: 'numeric', month: 'short' })
|
||||
: 'Nu'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<style define:vars={{color}}>
|
||||
.content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.titleRow {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.titleContainer {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 0.3rem 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.45;
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.dateContainer {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dateContainer p {
|
||||
font-size: 0.8rem;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.titleRow {
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
.dateContainer p {
|
||||
font-size: 0.74rem;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
34
src/components/Experience/Page.astro
Normal file
34
src/components/Experience/Page.astro
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
import { siteData } from '../../data/site';
|
||||
import ExperienceBox from './ExperienceBox.astro';
|
||||
import TitleCard from '../TitleCard.astro';
|
||||
import Section from '../Section.astro';
|
||||
const experience = [...siteData.experience];
|
||||
const sortedExperience = experience.sort((a, b) => b.start.getTime() - a.start.getTime());
|
||||
---
|
||||
<Section className="experience-section" label="Ervaring">
|
||||
<div class="experience-list">
|
||||
<TitleCard title="Ervaringen" description="Hieronder zie je alle werkervaring die ik tot nu toe hebv opgedaan. "/>
|
||||
{sortedExperience.map((experience) => (
|
||||
<ExperienceBox experience={experience} />
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<style>
|
||||
|
||||
.experience-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.3rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Mobile: collapse to one column,
|
||||
so "Kennis" ends up fully under experience */
|
||||
@media (max-width: 768px) {
|
||||
.experience-section {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
const { label, href } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="button">
|
||||
<a href={href} target="_blank">
|
||||
{label}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--border: 128, 115, 100;
|
||||
--transition-time: .3s;
|
||||
}
|
||||
.button {
|
||||
aspect-ratio: 6/2;
|
||||
min-width: 130px;
|
||||
width: 10dvw;
|
||||
border-radius: 10pt;
|
||||
border: 2px solid rgb(var(--border));
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-time);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: rgb(var(--border));
|
||||
}
|
||||
.button:hover a {
|
||||
color: white;
|
||||
transition: all var(--transition-time);
|
||||
}
|
||||
|
||||
.button a {
|
||||
text-decoration: none;
|
||||
color: black
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.button {
|
||||
min-width: 28%;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
132
src/components/Hero/EmailButton.astro
Normal file
132
src/components/Hero/EmailButton.astro
Normal file
@@ -0,0 +1,132 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import { siteData } from '../../data/site';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const { label, icon } = Astro.props;
|
||||
const safeEmail = siteData.email;
|
||||
const clipboardHandler = `navigator.clipboard.writeText('${safeEmail}').then(()=>{const btn=this.querySelector('.copy-button') || this; btn.dataset.copied='true'; setTimeout(()=>{ delete btn.dataset.copied }, 1200)})`;
|
||||
---
|
||||
|
||||
<div class="email-button" onclick={clipboardHandler}>
|
||||
<Icon name={icon} class="icon" />
|
||||
<button
|
||||
type="button"
|
||||
class="copy-button"
|
||||
onclick={clipboardHandler}
|
||||
aria-label="Copy email address"
|
||||
>
|
||||
<span class="label">{label}</span>
|
||||
<span class="email">{safeEmail}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--transition-time: .28s;
|
||||
}
|
||||
|
||||
.email-button {
|
||||
min-width: 130px;
|
||||
width: min(13rem, 100%);
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
background: rgba(18, 18, 28, 0.24);
|
||||
backdrop-filter: blur(2px);
|
||||
box-shadow: 0 24px 50px rgba(0, 0, 0, 0.18);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 1.8rem;
|
||||
|
||||
cursor: pointer;
|
||||
transition: transform var(--transition-time), border-color var(--transition-time), background var(--transition-time), box-shadow var(--transition-time);
|
||||
gap: 0.75rem;
|
||||
padding: 0.95rem 1.2rem;
|
||||
}
|
||||
|
||||
.email-button:hover {
|
||||
background: rgba(32, 32, 46, 0.5);
|
||||
border-color: rgba(255, 255, 255, 0.22);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 24px 65px rgba(0, 0, 0, 0.22);
|
||||
width: min(15rem, 100%);
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.email-button:hover .label {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.email-button:hover .email {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
text-decoration: none;
|
||||
color: #f5f2eb;
|
||||
text-align: left !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.email {
|
||||
position: absolute;
|
||||
display:none;
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-time) ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
transition: opacity var(--transition-time) ease;
|
||||
}
|
||||
|
||||
.email-button:hover .label {
|
||||
opacity: 0;
|
||||
display:none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.email-button:hover .email {
|
||||
position: static;
|
||||
display:block;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.copy-button[data-copied='true']::after {
|
||||
content: 'Copied!';
|
||||
position: absolute;
|
||||
right: 0.2rem;
|
||||
top: -1.4rem;
|
||||
padding: 0.2rem 0.55rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(18, 18, 18, 0.92);
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
flex-shrink: 0;
|
||||
color: #f5f2eb;
|
||||
}
|
||||
</style>
|
||||
@@ -1,56 +1,104 @@
|
||||
---
|
||||
import { siteData } from '../../data/site';
|
||||
import {age} from "../../utils/getAge"
|
||||
import {study} from "../../utils/getCurrentStudy"
|
||||
|
||||
import Button from "./Button.astro"
|
||||
import Button from "../Button.astro"
|
||||
import EmailButton from "./EmailButton.astro"
|
||||
import Section from '../Section.astro';
|
||||
|
||||
|
||||
const socials = [...siteData.socials];
|
||||
const occupation = siteData.occupation;
|
||||
---
|
||||
|
||||
<div id="container">
|
||||
<Section className="hero-section" label="Hero" isHero={true}>
|
||||
<div class="titleContainer">
|
||||
<h1 class="main-title">Valentijn van der Jagt</h1>
|
||||
<h3 class="sub-title">{age} - {study?.label}</h3>
|
||||
<h3 class="sub-title">{age} Jaar - {occupation}</h3>
|
||||
<h3></h3>
|
||||
</div>
|
||||
|
||||
<div class="buttonContainer">
|
||||
<Button label="Gitea" href="https://git.herpiederpiee.nl/valentijn?tab=repositories"/>
|
||||
<Button label="LinkedIn", href="https://nl.linkedin.com/in/valentijn-van-der-jagt"/>
|
||||
<Button label="E-mail" href="mailto:valentijn@example.com"/>
|
||||
{socials.map((link) => (
|
||||
<Button icon={link.icon} label={link.platform} href={link.url} />
|
||||
))}
|
||||
<EmailButton icon="mdi:email-outline" label="E-mail" />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<style>
|
||||
|
||||
#container {
|
||||
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
|
||||
height: 60dvh;
|
||||
margin: 10px 10px 0px 10px;
|
||||
background:rgb(172, 146, 116);
|
||||
border-radius: 10pt;
|
||||
.buttonContainer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
margin-top: 1.4rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
padding-left:10px;
|
||||
.buttonContainer > * {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.titleContainer {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
gap:10px;
|
||||
flex-direction: column;
|
||||
gap: 0.9rem;
|
||||
}
|
||||
|
||||
h1, h3 {
|
||||
font-family: "Work Sans", sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-style: normal;
|
||||
margin: 0;
|
||||
color: #ecfbff;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: max(min(8dvw, 130px), 30px);
|
||||
font-size: clamp(2.5rem, 6vw, 5.25rem);
|
||||
font-weight: 700;
|
||||
line-height: 0.95;
|
||||
letter-spacing: -0.03em;
|
||||
animation: floatText 10s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: max(min(3dvw, 90px), 18px);
|
||||
font-size: clamp(1.5rem, 2.5vw, 2.5rem);
|
||||
font-weight: 300;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-top: 0.7rem;
|
||||
opacity: 0.94;
|
||||
}
|
||||
|
||||
@keyframes floatText {
|
||||
0% { transform: translateY(0); }
|
||||
100% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
@keyframes driftBackground {
|
||||
0% { background-position: center top; }
|
||||
100% { background-position: 12% 62%; }
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.buttonContainer {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.buttonContainer {
|
||||
justify-content: center;
|
||||
gap: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.buttonContainer {
|
||||
justify-content: space-around;
|
||||
padding-left:0
|
||||
justify-content: center;
|
||||
gap: 0.8rem;
|
||||
padding: 0.75rem 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
35
src/components/Projects/Page.astro
Normal file
35
src/components/Projects/Page.astro
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
import { siteData } from '../../data/site';
|
||||
import ProjectBox from './ProjectBox.astro';
|
||||
import TitleCard from '../TitleCard.astro';
|
||||
import Section from '../Section.astro';
|
||||
|
||||
const projects = [...siteData.projects];
|
||||
---
|
||||
|
||||
<Section className="projects-section" label="Projecten">
|
||||
<div class="projects-list">
|
||||
<TitleCard title="Projecten" description="Hieronder zie je een aantal van mijn favoriete persoonlijke projecten!"/>
|
||||
{projects.map((project) => (
|
||||
<ProjectBox project={project} />
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
|
||||
<style>
|
||||
.projects-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.3rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Mobile: collapse to one column,
|
||||
so "Kennis" ends up fully under studies */
|
||||
@media (max-width: 768px) {
|
||||
.projects-section {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
102
src/components/Projects/ProjectBox.astro
Normal file
102
src/components/Projects/ProjectBox.astro
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
const { project } = Astro.props;
|
||||
import {techColors} from "../../data/site"
|
||||
|
||||
|
||||
const colors: { [id: string] : string; } = {...techColors}
|
||||
|
||||
import Button from "../Button.astro";
|
||||
import Box from "../Box.astro";
|
||||
---
|
||||
|
||||
<Box>
|
||||
<div class="titleRow">
|
||||
<div class="titleContainer">
|
||||
|
||||
<h3 class="main-title">{project.title}</h3>
|
||||
<p class="sub-title">{project.description}</p>
|
||||
<div class="tech-stack">
|
||||
{project.tech.map((item: string) => {
|
||||
const color = colors[item] || colors["Default"];
|
||||
return (
|
||||
<span class="tech-badge" style={`--brand-color: ${color}`}>
|
||||
{item}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<Button label="Bekijk Project" href={project.link} icon="mdi:link"/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<style>
|
||||
.titleRow {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.titleContainer {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 0.3rem 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.45;
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.tech-stack {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
margin-top: 1rem; /* Space between tags and title */
|
||||
}
|
||||
|
||||
.tech-badge {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 0.2rem 0.6rem;
|
||||
|
||||
/* The Magic: Using the variable from the style prop */
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1.5px solid var(--brand-color);
|
||||
color: var(--brand-color);
|
||||
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Keep your existing media queries */
|
||||
@media (max-width: 768px) {
|
||||
.titleRow {
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
/* Ensure badges don't get too small on mobile */
|
||||
.tech-stack {
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
74
src/components/Section.astro
Normal file
74
src/components/Section.astro
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
|
||||
interface Props {
|
||||
className: string;
|
||||
label: string;
|
||||
isHero?: boolean;
|
||||
}
|
||||
|
||||
const { className, label, isHero } = Astro.props;
|
||||
import hero_img from '../assets/hero.png';
|
||||
|
||||
|
||||
let containerBackground;
|
||||
if (isHero) {
|
||||
containerBackground = `radial-gradient(circle at 15% 18%, rgba(255, 246, 228, 0.32), transparent 24%),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.1), rgba(10, 10, 10, 0.24)),
|
||||
url("${hero_img.src}")`;
|
||||
} else {
|
||||
containerBackground = `linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02)),
|
||||
rgb(74, 74, 74)`;
|
||||
}
|
||||
|
||||
---
|
||||
<div id="container">
|
||||
<section class={className} aria-label={label}>
|
||||
<slot/>
|
||||
</section>
|
||||
</div>
|
||||
<style define:vars={{containerBackground}}>
|
||||
|
||||
|
||||
#container {
|
||||
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
|
||||
position: relative;
|
||||
margin: 10px;
|
||||
padding: clamp(1.5rem, 3vw, 2.5rem);
|
||||
|
||||
|
||||
background: var(--containerBackground);
|
||||
|
||||
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 24px 60px rgba(16, 14, 12, 0.18);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
animation: driftBackground 10s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
/* Mobile: collapse to one column,
|
||||
so "Kennis" ends up fully under studies */
|
||||
@media (max-width: 768px) {
|
||||
#container {
|
||||
margin: 5px;
|
||||
padding: 1rem;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes driftBackground {
|
||||
0% { background-position: center top; }
|
||||
100% { background-position: 12% 62%; }
|
||||
}
|
||||
|
||||
</style>
|
||||
29
src/components/Studies/Page.astro
Normal file
29
src/components/Studies/Page.astro
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
import { siteData } from '../../data/site';
|
||||
import StudyBox from './StudyBox.astro';
|
||||
import TitleCard from '../TitleCard.astro';
|
||||
import Section from '../Section.astro';
|
||||
|
||||
const studies = [...siteData.studies];
|
||||
const sortedStudies = studies.sort((a, b) => b.start.getTime() - a.start.getTime());
|
||||
---
|
||||
|
||||
<Section className="studies-section" label="Studies">
|
||||
<div class="studies-list">
|
||||
<TitleCard title="Opleidingen" description="Hieronder zie je alle opleidingen die ik heb gevolgd tot nu toe. "/>
|
||||
{sortedStudies.map((study) => (
|
||||
<StudyBox study={study} />
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<style>
|
||||
.studies-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.3rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
127
src/components/Studies/StudyBox.astro
Normal file
127
src/components/Studies/StudyBox.astro
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
const { study } = Astro.props;
|
||||
import Box from "../Box.astro";
|
||||
const color: string = study.color
|
||||
---
|
||||
|
||||
|
||||
|
||||
<Box accent={color}>
|
||||
<div class="titleRow">
|
||||
<div class="titleContainer">
|
||||
<h3 class="main-title">{study.study}</h3>
|
||||
<p class="sub-title">{study.school} · {study.level}</p>
|
||||
</div>
|
||||
|
||||
<div class="dateContainer">
|
||||
<p>
|
||||
{study.start.toLocaleDateString('en-US', { year: 'numeric', month: 'short' })}
|
||||
-
|
||||
{study.end
|
||||
? study.end.toLocaleDateString('en-US', { year: 'numeric', month: 'short' })
|
||||
: 'Nu'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<style define:vars={{color}}>
|
||||
.container {
|
||||
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
min-height: 120px;
|
||||
padding: 1rem 1rem 1rem 0.9rem;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.18);
|
||||
color: white;
|
||||
transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease;
|
||||
}
|
||||
|
||||
.container:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 14px 28px rgba(0,0,0,0.24);
|
||||
border-color: rgba(255,255,255,0.14);
|
||||
}
|
||||
|
||||
.accent {
|
||||
width: 4px;
|
||||
border-radius: 999px;
|
||||
background: var(--color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.titleRow {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.titleContainer {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 0.3rem 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.45;
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.dateContainer {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dateContainer p {
|
||||
font-size: 0.8rem;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
min-height: 100px;
|
||||
padding: 0.85rem 0.85rem 0.85rem 0.75rem;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.titleRow {
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
.dateContainer p {
|
||||
font-size: 0.74rem;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
53
src/components/TitleCard.astro
Normal file
53
src/components/TitleCard.astro
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
const { title, description } = Astro.props;
|
||||
import Box from "./Box.astro";
|
||||
---
|
||||
<Box isTitle={true}>
|
||||
<div class="titleRow">
|
||||
<div class="titleContainer">
|
||||
<h2 class="main-title">{title}</h2>
|
||||
<p class="sub-title">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<style>
|
||||
.titleRow {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.titleContainer {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: clamp(1.4rem,2vw,1.9rem);
|
||||
line-height: 1.2;
|
||||
margin: 0 0 0.3rem 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.45;
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.titleRow {
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,20 +1,84 @@
|
||||
export const siteData = {
|
||||
/*
|
||||
Personal Information
|
||||
*/
|
||||
name: "Valentijn van der Jagt",
|
||||
email: "valentijnkijkuit@outlook.com",
|
||||
about: "Softwareontwikkelaar met passie voor praktische oplossingen. Ervaring met Java, Python, IoT en moderne webtechnologieën. Momenteel student Technische Informatica aan Hogeschool Rotterdam.",
|
||||
occupation: "Student Technische Informatica",
|
||||
birthDate: new Date("2006-06-27"),
|
||||
|
||||
experience: [
|
||||
{
|
||||
name: "Brightpark",
|
||||
role: "Stagaire Junior Developer",
|
||||
description: "Bij Brightpark heb ik mijn eerste ervaring opgedaan in het werkveld van Software Ontwikkeling. Ik heb hier gewerkt aan meerdere projecten. Een van de leukere was een iOS app die helpt bij het meten van huizen door gebruik te maken van de Bosch GLM- series meetapperatuur. Deze app heb ik gemaakt in Flutter, met wat kleine stappen Swift in om de meetapperatuur werkend te krijgen.",
|
||||
start: new Date("2024-09-10"),
|
||||
end: new Date("2025-05-1"),
|
||||
tech: ["Flutter", "Swift", "Svelte", "Laravel"],
|
||||
color: "#30e7ffe9"
|
||||
}
|
||||
],
|
||||
|
||||
/*
|
||||
Online Presence
|
||||
*/
|
||||
socials: [
|
||||
{ icon: "mdi:git", platform: "Gitea", url: "https://git.herpiederpiee.nl/valentijn?tab=repositories" },
|
||||
{ icon: "mdi:linkedin", platform: "LinkedIn", url: "https://nl.linkedin.com/in/valentijn-van-der-jagt" },
|
||||
{ icon: "mdi:document", platform: "CV", url: "/CV.pdf"}
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
title: "Albert Heijn Bonus Scraper",
|
||||
description: "Een Java-applicatie die via Puppeteer real-time bonusacties scrapt en visualiseert. Bevat een krachtige zoekfunctionaliteit ondersteund door een zelfgeïmplementeerd fuzzy-search algoritme, waardoor er snel en flexibel door de weekelijkse aanbiedingen genavigeerd kan worden.",
|
||||
tech: ["Java", "Puppeteer", "HTML/CSS"],
|
||||
link: "https://git.herpiederpiee.nl/valentijn/Appie-Bonus-Scraper/"
|
||||
},
|
||||
{
|
||||
title: "Spotify Windows Integratie ",
|
||||
description: "Een Python-applicatie die via de Spotify Web API real-time playback-data synchroniseert met een custom Windows Taskbar-widget. Maakt gebruik van Tkinter voor een hardware-versnelde, transparante overlay die naadloos integreert met de Windows Shell-omgeving",
|
||||
tech: ["Python", "Tkinter", "Spotify API"],
|
||||
link: "https://git.herpiederpiee.nl/valentijn/Windows-Spotify-Taskbar/"
|
||||
},
|
||||
],
|
||||
|
||||
/*
|
||||
Academic Path
|
||||
*/
|
||||
studies: [
|
||||
{
|
||||
school: "Hogeschool Rotterdam",
|
||||
study: "Technische Informatica",
|
||||
level: "HBO",
|
||||
label: "Student Technische Informatica",
|
||||
start: new Date("2025-09-01"),
|
||||
end: null,
|
||||
color: "#D3104C"
|
||||
},
|
||||
{
|
||||
school: "Hogeschool Rotterdam",
|
||||
study: "Technische Informatica",
|
||||
school: "Grafisch Lyceum Rotterdam",
|
||||
study: "Software Developer",
|
||||
level: "MBO",
|
||||
label: "Student Software Developer",
|
||||
start: new Date("2021-09-01"),
|
||||
end: new Date("2025-06-18"),
|
||||
color: "#8FE508"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const techColors = {
|
||||
"Default": "#888888",
|
||||
"Java": "#ED8B00",
|
||||
"Python": "#3776AB",
|
||||
"HTML/CSS": "#E34F26",
|
||||
"Puppeteer": "#40B5A4",
|
||||
"Tkinter": "#3e759e",
|
||||
"Spotify API": "#1DB954",
|
||||
"C++": "#00599C",
|
||||
"Arduino": "#00979D",
|
||||
"Flutter": "#02569B",
|
||||
"Svelte": "#FF3E00",
|
||||
"Laravel": "#FF2D20",
|
||||
};
|
||||
@@ -7,6 +7,12 @@
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>Valentijn van der Jagt</title>
|
||||
|
||||
<!-- Work Sans -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
@@ -14,10 +20,17 @@
|
||||
</html>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin:0;
|
||||
padding:0;
|
||||
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #353535
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Hero from '../components/Hero/Page.astro';
|
||||
import Studies from "../components/Studies/Page.astro"
|
||||
import Projects from "../components/Projects/Page.astro"
|
||||
import Experience from "../components/Experience/Page.astro"
|
||||
import Contact from "../components/Contact/Page.astro"
|
||||
---
|
||||
|
||||
|
||||
<Layout>
|
||||
<Hero />
|
||||
<Projects/>
|
||||
<Experience/>
|
||||
<!-- <Studies/> -->
|
||||
<Contact/>
|
||||
</Layout>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { siteData } from '../data/site';
|
||||
|
||||
type Study = {
|
||||
school: string;
|
||||
study: string;
|
||||
label: string;
|
||||
start: Date;
|
||||
end: Date | null;
|
||||
};
|
||||
|
||||
|
||||
function getStudy(studies: Study[]){
|
||||
if (studies.length === 0) return null;
|
||||
|
||||
return studies.reduce((latest, study) => {
|
||||
if (latest.end === null) return latest;
|
||||
if (study.end === null) return study;
|
||||
|
||||
return study.start.getTime() > latest.start.getTime() ? study : latest;
|
||||
});
|
||||
}
|
||||
|
||||
export const study = getStudy(siteData.studies)
|
||||
Reference in New Issue
Block a user