feat(projects): add project scope

This commit is contained in:
2025-11-27 22:49:43 +01:00
parent da516c6e55
commit 8dcc3c660d
14 changed files with 166 additions and 146 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,7 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import SCOPE_BADGE_IMAGE from "~/assets/images/projects/top-screen/scope-badge.png";
const store = useProjectsStore(); const store = useProjectsStore();
const projectPreviews = useImages(...store.projects.map((x) => x.preview)); const [scopeBadgeImage, ...projectPreviews] = useImages(
SCOPE_BADGE_IMAGE,
...store.projects.map((x) => x.preview),
);
const getBounds = () => ({ const getBounds = () => ({
startIndex: Math.max(store.currentProject - 1, 0), startIndex: Math.max(store.currentProject - 1, 0),
@@ -72,6 +77,7 @@ useRender((ctx) => {
const x = baseX + 256 * offsetFromCenter + store.offsetX * (256 / 69); const x = baseX + 256 * offsetFromCenter + store.offsetX * (256 / 69);
const y = (192 - previewImage.height) / 2 + 10; const y = (192 - previewImage.height) / 2 + 10;
// game
ctx.save(); ctx.save();
ctx.translate(x, y); ctx.translate(x, y);
@@ -89,6 +95,7 @@ useRender((ctx) => {
ctx.drawImage(previewImage, 0, 0); ctx.drawImage(previewImage, 0, 0);
ctx.restore(); ctx.restore();
// technologies
const project = store.projects[i]!; const project = store.projects[i]!;
if (project.technologies && project.technologies.length > 0) { if (project.technologies && project.technologies.length > 0) {
const badgeY = previewImage.height - BADGE_HEIGHT - BADGE_MARGIN; const badgeY = previewImage.height - BADGE_HEIGHT - BADGE_MARGIN;
@@ -96,6 +103,15 @@ useRender((ctx) => {
drawTechBadges(ctx, project.technologies, badgeX, badgeY); drawTechBadges(ctx, project.technologies, badgeX, badgeY);
} }
// scope
ctx.translate(previewImage.width - scopeBadgeImage!.width + 3, -2);
ctx.drawImage(scopeBadgeImage!, 0, 0);
ctx.font = "7px NDS7";
ctx.fillStyle = "#000000";
ctx.textBaseline = "top";
fillTextCentered(ctx, project.scope.toUpperCase(), 0, -5, 40);
ctx.restore(); ctx.restore();
} }
}); });

View File

@@ -30,55 +30,53 @@ export const useContactStore = defineStore("contact", {
animateIntro() { animateIntro() {
this.isIntro = true; this.isIntro = true;
const start = 2; const timeline = gsap.timeline({
onComplete: () => {
this.isIntro = false;
},
});
gsap.fromTo( timeline
this.intro, .fromTo(
{ this.intro,
stage1Opacity: 0, {
titleY: SCREEN_HEIGHT, stage1Opacity: 0,
}, titleY: SCREEN_HEIGHT,
{
stage1Opacity: 1,
titleY: SCREEN_HEIGHT - 23,
duration: 0.1,
delay: start,
ease: "none",
},
);
gsap.fromTo(
this.intro,
{
stage2Opacity: 0,
},
{
stage2Opacity: 1,
duration: 0.1,
delay: start + 0.15,
ease: "none",
},
);
gsap.fromTo(
this.intro,
{
stage3Opacity: 0,
topBarY: -20,
bottomBarY: SCREEN_HEIGHT - 4,
},
{
stage3Opacity: 1,
topBarY: 0,
bottomBarY: SCREEN_HEIGHT - 24,
duration: 0.1,
delay: start + 0.15 + 0.15,
ease: "none",
onComplete: () => {
this.isIntro = false;
}, },
}, {
); stage1Opacity: 1,
titleY: SCREEN_HEIGHT - 23,
duration: 0.1,
ease: "none",
},
2,
)
.fromTo(
this.intro,
{ stage2Opacity: 0 },
{
stage2Opacity: 1,
duration: 0.1,
ease: "none",
},
2.15,
)
.fromTo(
this.intro,
{
stage3Opacity: 0,
topBarY: -20,
bottomBarY: SCREEN_HEIGHT - 4,
},
{
stage3Opacity: 1,
topBarY: 0,
bottomBarY: SCREEN_HEIGHT - 24,
duration: 0.1,
ease: "none",
},
2.3,
);
}, },
pushNotification(content: string) { pushNotification(content: string) {
@@ -94,49 +92,46 @@ export const useContactStore = defineStore("contact", {
animateOutro() { animateOutro() {
this.isOutro = true; this.isOutro = true;
gsap.fromTo( const timeline = gsap.timeline({
this.outro, onComplete: () => {
{ setTimeout(() => {
stage1Opacity: 1, this.isOutro = false;
navigateTo("/");
}, 2000);
}, },
{ });
stage1Opacity: 0,
duration: 0.2,
ease: "none",
},
);
gsap.fromTo( timeline
this.outro, .fromTo(
{ this.outro,
stage2Opacity: 1, { stage1Opacity: 1 },
}, {
{ stage1Opacity: 0,
stage2Opacity: 0, duration: 0.2,
duration: 0.25, ease: "none",
delay: 0.25,
ease: "none",
},
);
gsap.fromTo(
this.outro,
{
stage3Opacity: 1,
},
{
stage3Opacity: 0,
duration: 0.3,
delay: 0.5,
ease: "none",
onComplete: () => {
setTimeout(() => {
this.isOutro = false;
navigateTo("/");
}, 2000);
}, },
}, 0,
); )
.fromTo(
this.outro,
{ stage2Opacity: 1 },
{
stage2Opacity: 0,
duration: 0.25,
ease: "none",
},
0.25,
)
.fromTo(
this.outro,
{ stage3Opacity: 1 },
{
stage3Opacity: 0,
duration: 0.3,
ease: "none",
},
0.5,
);
}, },
}, },
}); });

View File

@@ -24,74 +24,72 @@ export const useHomeStore = defineStore("home", {
this.isIntro = true; this.isIntro = true;
const start = 0.5; const timeline = gsap.timeline({
const duration = 0.5; onComplete: () => {
const delay = 0.35; this.isIntro = false;
if (!appStore.booted) appStore.booted = true;
},
});
gsap.fromTo( timeline
this.intro, .fromTo(
{ stage1Opacity: 0 }, this.intro,
{ { stage1Opacity: 0 },
stage1Opacity: 1, {
duration, stage1Opacity: 1,
delay: start, duration: 0.5,
ease: "none", ease: "none",
onComplete: () => {
this.isIntro = false;
if (!appStore.booted) appStore.booted = true;
}, },
}, 0.5,
); )
.fromTo(
gsap.fromTo( this.intro,
this.intro, { statusBarY: -20 },
{ statusBarY: -20 }, {
{ statusBarY: 0,
statusBarY: 0, duration: 0.15,
duration: duration - delay, ease: "none",
delay: start + delay, },
ease: "none", 0.85,
}, );
);
}, },
animateOutro(to: "contact" | "projects" | "settings") { animateOutro(to: "contact" | "projects" | "settings") {
this.isOutro = true; this.isOutro = true;
this.outro.animateTop = to !== "settings"; this.outro.animateTop = to !== "settings";
const duration = 0.4; const timeline = gsap.timeline({
const delay = 0.08; onComplete: () => {
this.isOutro = true;
gsap.fromTo( navigateTo(`/${to}`);
this.outro,
{
buttonOffsetY: 0,
stage1Opacity: 1,
}, },
{ });
buttonOffsetY: -200,
stage1Opacity: 0, timeline
duration, .fromTo(
delay, this.outro,
ease: "none", { stage2Opacity: 1 },
onComplete: () => { {
this.isOutro = true; stage2Opacity: 0,
navigateTo(`/${to}`); duration: 0.16,
ease: "none",
}, },
}, 0,
); )
.fromTo(
gsap.fromTo( this.outro,
this.outro, {
{ buttonOffsetY: 0,
stage2Opacity: 1, stage1Opacity: 1,
}, },
{ {
stage2Opacity: 0, buttonOffsetY: -200,
ease: "none", stage1Opacity: 0,
duration: duration / 2 - delay, duration: 0.4,
}, ease: "none",
); },
0.08,
);
}, },
}, },
}); });

View File

@@ -66,6 +66,7 @@ export const useProjectsStore = defineStore("projects", {
preview: string; preview: string;
url: string | null; url: string | null;
technologies: string[]; technologies: string[];
scope: "hobby" | "work";
body: MarkdownBody; body: MarkdownBody;
}[], }[],
currentProject: 0, currentProject: 0,
@@ -92,6 +93,7 @@ export const useProjectsStore = defineStore("projects", {
preview: `/images/projects/${id}/preview.webp`, preview: `/images/projects/${id}/preview.webp`,
url: project.url, url: project.url,
technologies: project.technologies, technologies: project.technologies,
scope: project.scope,
body: simplifyMarkdownAST(project.body), body: simplifyMarkdownAST(project.body),
}); });
} }

View File

@@ -11,6 +11,7 @@ export default defineContentConfig({
url: z.url().nullable(), url: z.url().nullable(),
order: z.number(), order: z.number(),
technologies: z.array(z.string()), technologies: z.array(z.string()),
scope: z.enum(["hobby", "work"]),
}), }),
}), }),
}, },

View File

@@ -3,4 +3,5 @@ description: Biobleud - Automated Excel imports\nfor an ERP system
url: null url: null
order: 100 order: 100
technologies: [VBA] technologies: [VBA]
scope: work
--- ---

View File

@@ -3,6 +3,7 @@ description: LBF Bot - Custom Discord bot for\na gaming group
url: https://github.com/pihkaal/lbf-bot url: https://github.com/pihkaal/lbf-bot
order: 50 order: 50
technologies: [Node, TypeScript] technologies: [Node, TypeScript]
scope: hobby
--- ---
a a

View File

@@ -3,4 +3,5 @@ description: lilou.cat - My cat's website
url: https://lilou.cat url: https://lilou.cat
order: 40 order: 40
technologies: [HTML, Go] technologies: [HTML, Go]
scope: hobby
--- ---

View File

@@ -3,6 +3,7 @@ description: pihkaal.me - my personal website
url: https://pihkaal.me url: https://pihkaal.me
order: 10 order: 10
technologies: [Nuxt, TypeScript] technologies: [Nuxt, TypeScript]
scope: hobby
--- ---
# pihkaal.me # pihkaal.me

View File

@@ -2,6 +2,7 @@
description: Raylib Speedruns - Collection of simple\nRaylib setups in multiple languages description: Raylib Speedruns - Collection of simple\nRaylib setups in multiple languages
url: https://github.com/pihkaal/raylib-speedruns url: https://github.com/pihkaal/raylib-speedruns
order: 60 order: 60
scope: hobby
--- ---
# Raylib Speedruns # Raylib Speedruns

View File

@@ -3,4 +3,5 @@ description: S3PWeb - Intership and\napprenticeship
url: null url: null
order: 80 order: 80
technologies: [Node, StencilJS, TypeScript] technologies: [Node, StencilJS, TypeScript]
scope: work
--- ---

View File

@@ -3,4 +3,5 @@ description: Simple QR - Simple QR code generator\nwith straightforward API
url: https://simple-qr.com url: https://simple-qr.com
order: 30 order: 30
technologies: [Nuxt, TypeScript] technologies: [Nuxt, TypeScript]
scope: hobby
--- ---

View File

@@ -3,4 +3,5 @@ description: tlock - fully customizable and cross-\nplatform terminal based cloc
url: https://github.com/pihkaal/tlock url: https://github.com/pihkaal/tlock
order: 20 order: 20
technologies: [Rust] technologies: [Rust]
scope: hobby
--- ---