Compare commits
18 Commits
fefd1e171a
...
6dcf03a38a
| Author | SHA1 | Date | |
|---|---|---|---|
| 6dcf03a38a | |||
| c03c87c1f6 | |||
| d11aae080a | |||
| 54958437c4 | |||
| 712cb087d2 | |||
| edaf11d2bf | |||
| 7224dd29d7 | |||
| 3dfed22a28 | |||
| 35833a3fb4 | |||
| 4141580ac1 | |||
| ec75f4777b | |||
| 05d34811d3 | |||
| 4a7a1028b7 | |||
| eba1b8d84c | |||
| 3216d6e79e | |||
| 7e416c2b02 | |||
| 8d18e1ddbb | |||
| 1827659d3d |
@@ -4,8 +4,7 @@ const { locale } = useI18n();
|
||||
const app = useAppStore();
|
||||
const pageTitle = computed(() => {
|
||||
if (!app.booted) return "Pihkaal";
|
||||
const name = app.screen.charAt(0).toUpperCase() + app.screen.slice(1);
|
||||
return `${name} - Pihkaal`;
|
||||
return `${$t(`screens.${app.screen}`)} - Pihkaal`;
|
||||
});
|
||||
|
||||
useHead({
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
@font-face {
|
||||
font-family: "NDS39";
|
||||
src: url("/assets/fonts/nds-39px.ttf") format("truetype");
|
||||
src: url("/assets/fonts/nds-39px.woff2") format("woff2");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/assets/fonts/nds-39px.woff2
Normal file
BIN
app/assets/fonts/nds-39px.woff2
Normal file
Binary file not shown.
Binary file not shown.
@@ -42,7 +42,11 @@ onRender((ctx) => {
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
|
||||
const lines = confirmationModal.text.split("\n").slice(0, MAX_LINES);
|
||||
const rawText =
|
||||
typeof confirmationModal.text === "function"
|
||||
? confirmationModal.text()
|
||||
: confirmationModal.text;
|
||||
const lines = rawText.split("\n").slice(0, MAX_LINES);
|
||||
const totalTextHeight = lines.length * LINE_HEIGHT;
|
||||
const textStartY = BG_Y + Math.floor((BG_HEIGHT - totalTextHeight) / 2) - 2;
|
||||
|
||||
|
||||
@@ -70,15 +70,33 @@ onRender((ctx) => {
|
||||
|
||||
ctx.fillStyle = "#aaaaaa";
|
||||
ctx.font = "7px NDS7";
|
||||
fillTextHCentered(ctx, $t(`creditsScreen.${id}.label`), 0, y, LOGICAL_WIDTH);
|
||||
fillTextHCentered(
|
||||
ctx,
|
||||
$t(`creditsScreen.${id}.label`),
|
||||
0,
|
||||
y,
|
||||
LOGICAL_WIDTH,
|
||||
);
|
||||
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.font = "10px NDS10";
|
||||
fillTextHCentered(ctx, $t(`creditsScreen.${id}.author`), 0, y + CREDITS_LINE_HEIGHT, LOGICAL_WIDTH);
|
||||
fillTextHCentered(
|
||||
ctx,
|
||||
$t(`creditsScreen.${id}.author`),
|
||||
0,
|
||||
y + CREDITS_LINE_HEIGHT,
|
||||
LOGICAL_WIDTH,
|
||||
);
|
||||
|
||||
ctx.fillStyle = "#888888";
|
||||
ctx.font = "7px NDS7";
|
||||
fillTextHCentered(ctx, $t(`creditsScreen.${id}.url`), 0, y + CREDITS_LINE_HEIGHT * 2, LOGICAL_WIDTH);
|
||||
fillTextHCentered(
|
||||
ctx,
|
||||
$t(`creditsScreen.${id}.url`),
|
||||
0,
|
||||
y + CREDITS_LINE_HEIGHT * 2,
|
||||
LOGICAL_WIDTH,
|
||||
);
|
||||
}
|
||||
|
||||
ctx.globalAlpha = store.isIntro
|
||||
|
||||
@@ -49,15 +49,33 @@ onRender((ctx) => {
|
||||
|
||||
ctx.fillStyle = "#aaaaaa";
|
||||
ctx.font = "7px NDS7";
|
||||
fillTextHCentered(ctx, $t(`creditsScreen.${id}.label`), 0, y, LOGICAL_WIDTH);
|
||||
fillTextHCentered(
|
||||
ctx,
|
||||
$t(`creditsScreen.${id}.label`),
|
||||
0,
|
||||
y,
|
||||
LOGICAL_WIDTH,
|
||||
);
|
||||
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.font = "10px NDS10";
|
||||
fillTextHCentered(ctx, $t(`creditsScreen.${id}.author`), 0, y + CREDITS_LINE_HEIGHT, LOGICAL_WIDTH);
|
||||
fillTextHCentered(
|
||||
ctx,
|
||||
$t(`creditsScreen.${id}.author`),
|
||||
0,
|
||||
y + CREDITS_LINE_HEIGHT,
|
||||
LOGICAL_WIDTH,
|
||||
);
|
||||
|
||||
ctx.fillStyle = "#888888";
|
||||
ctx.font = "7px NDS7";
|
||||
fillTextHCentered(ctx, $t(`creditsScreen.${id}.url`), 0, y + CREDITS_LINE_HEIGHT * 2, LOGICAL_WIDTH);
|
||||
fillTextHCentered(
|
||||
ctx,
|
||||
$t(`creditsScreen.${id}.url`),
|
||||
0,
|
||||
y + CREDITS_LINE_HEIGHT * 2,
|
||||
LOGICAL_WIDTH,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ const store = useGalleryStore();
|
||||
const { assets } = useAssets();
|
||||
|
||||
onMounted(() => {
|
||||
store.cleanup();
|
||||
if (store.shouldAnimateOutro) {
|
||||
store.shouldAnimateOutro = false;
|
||||
store.animateOutro();
|
||||
|
||||
@@ -87,11 +87,11 @@ onRender((ctx) => {
|
||||
// gallery
|
||||
ctx.font = "7px NDS7";
|
||||
ctx.fillStyle = pressed.value === "gallery" ? "#2c2c2c" : "#282828";
|
||||
fillTextCentered(
|
||||
fillTextHCentered(
|
||||
ctx,
|
||||
$t("home.photoGallery"),
|
||||
132,
|
||||
78 + getButtonOffset("gallery"),
|
||||
85 + getButtonOffset("gallery"),
|
||||
87,
|
||||
);
|
||||
|
||||
@@ -127,12 +127,12 @@ onRender((ctx) => {
|
||||
140,
|
||||
);
|
||||
}
|
||||
ctx.textBaseline = "alphabetic";
|
||||
ctx.textBaseline = "top";
|
||||
|
||||
// gba thing
|
||||
ctx.font = "10px NDS10";
|
||||
ctx.fillStyle = "#a2a2a2";
|
||||
fillTextCentered(ctx, $t("home.greeting"), 79, 135, 140);
|
||||
fillTextHCentered(ctx, $t("home.greeting"), 79, 137, 140);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,14 +15,28 @@ const switch2d = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal :open="true" :dismissible="false" :title="$t('lagModal.title')" :ui="{ footer: 'justify-end' }">
|
||||
<UModal
|
||||
:open="true"
|
||||
:dismissible="false"
|
||||
:title="$t('lagModal.title')"
|
||||
:ui="{ footer: 'justify-end' }"
|
||||
>
|
||||
<template #body>
|
||||
{{ $t('lagModal.body') }}
|
||||
{{ $t("lagModal.body") }}
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<UButton variant="ghost" color="neutral" :label="$t('lagModal.keep3d')" @click="keep3d" />
|
||||
<UButton color="neutral" :label="$t('lagModal.switch2d')" @click="switch2d" />
|
||||
<UButton
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
:label="$t('lagModal.keep3d')"
|
||||
@click="keep3d"
|
||||
/>
|
||||
<UButton
|
||||
color="neutral"
|
||||
:label="$t('lagModal.switch2d')"
|
||||
@click="switch2d"
|
||||
/>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
@@ -74,8 +74,6 @@ const TOP_SCREEN_OFFSET = 170;
|
||||
|
||||
const zoomStyle = computed(() => {
|
||||
const scale = ndsScale.value * gallery.zoom.scale;
|
||||
if (scale === 1) return { scale: ndsScale.value };
|
||||
|
||||
const y = TOP_SCREEN_OFFSET * ndsScale.value * (gallery.zoom.scale - 1);
|
||||
return { transform: `translateY(${y}px) scale(${scale})` };
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ let timeline: gsap.core.Timeline | null = null;
|
||||
|
||||
const text = computed(() => {
|
||||
const project = store.projects[store.currentProject]!;
|
||||
return $t("projects.linkConformationPopup.text", {
|
||||
return $t("projects.linkConfirmationPopup.text", {
|
||||
url: project.link.replace(/^https?:\/\//, ""),
|
||||
});
|
||||
});
|
||||
@@ -155,13 +155,13 @@ onRender((ctx) => {
|
||||
|
||||
drawTextWithShadow(
|
||||
ctx,
|
||||
$t("projects.linkConformationPopup.yes").toUpperCase(),
|
||||
$t("projects.linkConfirmationPopup.yes").toUpperCase(),
|
||||
207,
|
||||
YES_Y - 3,
|
||||
);
|
||||
drawTextWithShadow(
|
||||
ctx,
|
||||
$t("projects.linkConformationPopup.no").toUpperCase(),
|
||||
$t("projects.linkConfirmationPopup.no").toUpperCase(),
|
||||
207,
|
||||
NO_Y - 3,
|
||||
);
|
||||
|
||||
@@ -119,7 +119,7 @@ onRender((ctx) => {
|
||||
ctx,
|
||||
"black",
|
||||
project.summary,
|
||||
Math.floor(185 - textWidth / 2),
|
||||
Math.floor(181 - textWidth / 2),
|
||||
17,
|
||||
);
|
||||
|
||||
|
||||
@@ -193,7 +193,9 @@ onMounted(() => {
|
||||
canvas.value.addEventListener("click", handleCanvasClick);
|
||||
canvas.value.addEventListener("mousedown", handleCanvasMouseDown);
|
||||
canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true });
|
||||
canvas.value.addEventListener("touchstart", handleTouchStart, { passive: false });
|
||||
canvas.value.addEventListener("touchstart", handleTouchStart, {
|
||||
passive: false,
|
||||
});
|
||||
canvas.value.addEventListener("touchend", handleTouchEnd, { passive: true });
|
||||
canvas.value.addEventListener("mousedown", handleSwipeMouseDown);
|
||||
canvas.value.addEventListener("mouseup", handleSwipeMouseUp);
|
||||
|
||||
@@ -4,7 +4,6 @@ import gsap from "gsap";
|
||||
const { locales, locale, setLocale } = useI18n();
|
||||
const store = useSettingsStore();
|
||||
const confirmationModal = useConfirmationModal();
|
||||
const achievements = useAchievementsStore();
|
||||
const { assets } = useAssets();
|
||||
const { onRender } = useScreen();
|
||||
|
||||
@@ -160,25 +159,24 @@ const handleActivateB = async () => {
|
||||
store.closeSubMenu();
|
||||
};
|
||||
|
||||
const AVAILABLE_LOCALES = ["en", "fr"];
|
||||
|
||||
const handleActivateA = () => {
|
||||
if (isAnimating.value) return;
|
||||
const selectedLocale = locales.value[BUTTON_KEYS.indexOf(selected.value)]!;
|
||||
|
||||
if (!AVAILABLE_LOCALES.includes(selectedLocale.code)) {
|
||||
confirmationModal.open({
|
||||
text: $t("settings.options.language.unavailable"),
|
||||
timeout: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setLocale(selectedLocale.code);
|
||||
|
||||
if (!achievements.advancement.languages.includes(selectedLocale.code)) {
|
||||
achievements.advancement.languages.push(selectedLocale.code);
|
||||
if (achievements.advancement.languages.length === locales.value.length) {
|
||||
achievements.unlock("settings_language_try_all");
|
||||
}
|
||||
}
|
||||
|
||||
confirmationModal.open({
|
||||
text: $t(
|
||||
"settings.options.language.confirmation",
|
||||
{},
|
||||
{ locale: selectedLocale.code },
|
||||
),
|
||||
text: () => $t("settings.options.language.confirmation"),
|
||||
onClosed: async () => {
|
||||
await animateOutro();
|
||||
store.closeSubMenu(true);
|
||||
|
||||
@@ -156,16 +156,28 @@ useKeyDown(({ key }) => {
|
||||
|
||||
switch (key) {
|
||||
case "NDS_UP":
|
||||
if (selectedRow > 0) { assets.audio.tinyClick.play(0.8); select(selectedCol, selectedRow - 1); }
|
||||
if (selectedRow > 0) {
|
||||
assets.audio.tinyClick.play(0.8);
|
||||
select(selectedCol, selectedRow - 1);
|
||||
}
|
||||
break;
|
||||
case "NDS_RIGHT":
|
||||
if (selectedCol < GRID_SIZE - 1) { assets.audio.tinyClick.play(0.8); select(selectedCol + 1, selectedRow); }
|
||||
if (selectedCol < GRID_SIZE - 1) {
|
||||
assets.audio.tinyClick.play(0.8);
|
||||
select(selectedCol + 1, selectedRow);
|
||||
}
|
||||
break;
|
||||
case "NDS_DOWN":
|
||||
if (selectedRow < GRID_SIZE - 1) { assets.audio.tinyClick.play(0.8); select(selectedCol, selectedRow + 1); }
|
||||
if (selectedRow < GRID_SIZE - 1) {
|
||||
assets.audio.tinyClick.play(0.8);
|
||||
select(selectedCol, selectedRow + 1);
|
||||
}
|
||||
break;
|
||||
case "NDS_LEFT":
|
||||
if (selectedCol > 0) { assets.audio.tinyClick.play(0.8); select(selectedCol - 1, selectedRow); }
|
||||
if (selectedCol > 0) {
|
||||
assets.audio.tinyClick.play(0.8);
|
||||
select(selectedCol - 1, selectedRow);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -201,13 +201,11 @@ onRender((ctx) => {
|
||||
ctx.font = "10px NDS10";
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.letterSpacing = "0px";
|
||||
fillTextCentered(
|
||||
fillTextHCentered(
|
||||
ctx,
|
||||
props.title,
|
||||
props.x + 1,
|
||||
// TODO: -10 is needed because fillTextCentered isn't using top baseline
|
||||
// i will change that in the future (maybe)
|
||||
Y + ARROW_IMAGE_HEIGHT * 2 + SQUARE_HEIGHT - 6,
|
||||
Y + ARROW_IMAGE_HEIGHT * 2 + SQUARE_HEIGHT + 3,
|
||||
downImage.value.rect.width,
|
||||
);
|
||||
}, 10);
|
||||
|
||||
@@ -10,10 +10,14 @@ export const useKeyDown = (callback: KeyDownCallback) => {
|
||||
const app = useAppStore();
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (app.lagDetected) return;
|
||||
if (app.lagModalOpen) return;
|
||||
|
||||
const ndsButton = mapCodeToNDS(event.code);
|
||||
if (ndsButton && document.activeElement && document.activeElement !== document.body) {
|
||||
if (
|
||||
ndsButton &&
|
||||
document.activeElement &&
|
||||
document.activeElement !== document.body
|
||||
) {
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import type { InternalApi } from "nitropack/types";
|
||||
import { promiseTimeout, useElementSize } from "@vueuse/core";
|
||||
import gsap from "gsap";
|
||||
|
||||
const { t } = useI18n();
|
||||
useHead({
|
||||
title: "Gallery - Pihkaal",
|
||||
title: computed(() => `${t("screens.gallery")} - Pihkaal`),
|
||||
});
|
||||
|
||||
const { data: images } = await useAsyncData(
|
||||
@@ -178,7 +179,7 @@ onMounted(() => {
|
||||
gap: 24,
|
||||
estimateSize: 510,
|
||||
}"
|
||||
class="p-6 lg:p-8 xl:p-10 2xl:p-12 h-screen bg-[#0a0a0a] text-white font-[JetBrains_Mono] focus:outline-none"
|
||||
class="p-4 md:p-6 lg:p-8 xl:p-10 2xl:p-12 h-screen bg-[#0a0a0a] text-white font-[JetBrains_Mono] focus:outline-none"
|
||||
:class="{ 'no-scroll': isAnimating }"
|
||||
tabindex="0"
|
||||
>
|
||||
|
||||
@@ -12,8 +12,12 @@ const lagModal = overlay.create(LazyLagModal);
|
||||
|
||||
watch(
|
||||
() => app.lagDetected,
|
||||
(detected) => {
|
||||
if (detected) lagModal.open();
|
||||
async (detected) => {
|
||||
if (detected) {
|
||||
app.lagModalOpen = true;
|
||||
await lagModal.open();
|
||||
app.lagModalOpen = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -28,17 +32,17 @@ const isTouchDevice = () =>
|
||||
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const isLargeEnough = computed(
|
||||
() => windowSize.width.value / windowSize.height.value > 614 / 667,
|
||||
);
|
||||
|
||||
watch([windowSize.width, windowSize.height], ([width, height]) => {
|
||||
watch(
|
||||
[windowSize.width, windowSize.height],
|
||||
([width, height]) => {
|
||||
if (width / height > 614 / 667) {
|
||||
if (app.booted) app.allowHints();
|
||||
} else {
|
||||
app.disallowHints();
|
||||
}
|
||||
}, { immediate: true });
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const helpButton = useTemplateRef("helpButton");
|
||||
let helpAnimation: gsap.core.Timeline | null = null;
|
||||
|
||||
@@ -23,7 +23,6 @@ export const ACHIEVEMENTS = [
|
||||
{ id: "taptap_score_20", secret: false },
|
||||
// secrets
|
||||
{ id: "settings_color_try_all", secret: true },
|
||||
{ id: "settings_language_try_all", secret: true },
|
||||
{ id: "settings_visit_all", secret: true },
|
||||
{ id: "contact_36_notifications", secret: true },
|
||||
] as const;
|
||||
@@ -32,7 +31,6 @@ export type Achievement = (typeof ACHIEVEMENTS)[number]["id"];
|
||||
|
||||
export const useAchievementsStore = defineStore("achievements", () => {
|
||||
const app = useAppStore();
|
||||
const { locale } = useI18n();
|
||||
|
||||
const storage = useLocalStorage(
|
||||
STORAGE_ID,
|
||||
@@ -40,7 +38,6 @@ export const useAchievementsStore = defineStore("achievements", () => {
|
||||
unlocked: [] as Achievement[],
|
||||
advancement: {
|
||||
colors: [app.color.hex],
|
||||
languages: [locale.value],
|
||||
visitedSettings: [] as string[],
|
||||
},
|
||||
},
|
||||
@@ -50,9 +47,6 @@ export const useAchievementsStore = defineStore("achievements", () => {
|
||||
if (!storage.value.advancement.colors.includes(app.color.hex)) {
|
||||
storage.value.advancement.colors.push(app.color.hex);
|
||||
}
|
||||
if (!storage.value.advancement.languages.includes(locale.value)) {
|
||||
storage.value.advancement.languages.push(locale.value);
|
||||
}
|
||||
|
||||
const confetti = useConfetti();
|
||||
|
||||
@@ -80,7 +74,6 @@ export const useAchievementsStore = defineStore("achievements", () => {
|
||||
unlocked: [],
|
||||
advancement: {
|
||||
colors: [app.color.hex],
|
||||
languages: [locale.value],
|
||||
visitedSettings: [],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -57,6 +57,7 @@ export const useAppStore = defineStore("app", {
|
||||
hintsVisible: false,
|
||||
hintsAllowed: false,
|
||||
lagDetected: false,
|
||||
lagModalOpen: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ const MODAL_TIME = 0.25;
|
||||
export const useConfirmationModal = defineStore("confirmationModal", {
|
||||
state: () => ({
|
||||
isOpen: false,
|
||||
text: "",
|
||||
text: "" as string | (() => string),
|
||||
aLabel: null as string | null,
|
||||
bLabel: null as string | null,
|
||||
onActivateA: null as (() => void) | null,
|
||||
@@ -24,7 +24,7 @@ export const useConfirmationModal = defineStore("confirmationModal", {
|
||||
|
||||
actions: {
|
||||
open(options: {
|
||||
text: string;
|
||||
text: string | (() => string);
|
||||
aLabel?: string;
|
||||
bLabel?: string;
|
||||
onActivateA?: () => void;
|
||||
|
||||
@@ -43,7 +43,7 @@ export const useContactStore = defineStore("contact", {
|
||||
duration: 0.1,
|
||||
ease: "none",
|
||||
},
|
||||
2,
|
||||
1,
|
||||
)
|
||||
.fromTo(
|
||||
this.intro,
|
||||
@@ -53,7 +53,7 @@ export const useContactStore = defineStore("contact", {
|
||||
duration: 0.1,
|
||||
ease: "none",
|
||||
},
|
||||
2.15,
|
||||
1.15,
|
||||
)
|
||||
.fromTo(
|
||||
this.intro,
|
||||
@@ -67,7 +67,7 @@ export const useContactStore = defineStore("contact", {
|
||||
duration: 0.1,
|
||||
ease: "none",
|
||||
},
|
||||
2.3,
|
||||
1.3,
|
||||
)
|
||||
.call(() => {
|
||||
this.isIntro = false;
|
||||
@@ -104,7 +104,7 @@ export const useContactStore = defineStore("contact", {
|
||||
this.isOutro = false;
|
||||
const app = useAppStore();
|
||||
app.navigateTo("home");
|
||||
}, 2000);
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -11,12 +11,14 @@ const ANIMATION = {
|
||||
NDS_CAMERA_ROTATION: new THREE.Euler(THREE.MathUtils.degToRad(-55), 0, 0),
|
||||
GALLERY_CAMERA_POSITION: new THREE.Vector3(0, 4.5, -3),
|
||||
GALLERY_CAMERA_ROTATION: new THREE.Euler(THREE.MathUtils.degToRad(-62), 0, 0),
|
||||
CAMERA_DURATION: 3,
|
||||
CAMERA_DURATION_INTRO: 2.115,
|
||||
CAMERA_DURATION_OUTRO: 2.037,
|
||||
CAMERA_ROTATION_OVERLAP: 0.1,
|
||||
|
||||
// 2D zoom
|
||||
ZOOM_SCALE: 6,
|
||||
ZOOM_DURATION: 3,
|
||||
ZOOM_DURATION_INTRO: 2.115,
|
||||
ZOOM_DURATION_OUTRO: 2.037,
|
||||
ZOOM_EASE: "power2.inOut",
|
||||
};
|
||||
|
||||
@@ -40,6 +42,12 @@ export const useGalleryStore = defineStore("gallery", {
|
||||
}),
|
||||
|
||||
actions: {
|
||||
cleanup() {
|
||||
gsap.killTweensOf(this.zoom);
|
||||
gsap.killTweensOf(this.intro);
|
||||
gsap.killTweensOf(this.outro);
|
||||
},
|
||||
|
||||
animateIntro() {
|
||||
this.isIntro = true;
|
||||
this.isOutro = false;
|
||||
@@ -73,7 +81,7 @@ export const useGalleryStore = defineStore("gallery", {
|
||||
x: ANIMATION.GALLERY_CAMERA_POSITION.x,
|
||||
y: ANIMATION.GALLERY_CAMERA_POSITION.y,
|
||||
z: ANIMATION.GALLERY_CAMERA_POSITION.z,
|
||||
duration: ANIMATION.CAMERA_DURATION,
|
||||
duration: ANIMATION.CAMERA_DURATION_INTRO,
|
||||
delay: zoomDelay,
|
||||
ease: ANIMATION.ZOOM_EASE,
|
||||
},
|
||||
@@ -91,7 +99,7 @@ export const useGalleryStore = defineStore("gallery", {
|
||||
y: ANIMATION.GALLERY_CAMERA_ROTATION.y,
|
||||
z: ANIMATION.GALLERY_CAMERA_ROTATION.z,
|
||||
duration:
|
||||
ANIMATION.CAMERA_DURATION *
|
||||
ANIMATION.CAMERA_DURATION_INTRO *
|
||||
(1 - ANIMATION.CAMERA_ROTATION_OVERLAP),
|
||||
delay: zoomDelay,
|
||||
ease: ANIMATION.ZOOM_EASE,
|
||||
@@ -103,7 +111,7 @@ export const useGalleryStore = defineStore("gallery", {
|
||||
{ scale: 1 },
|
||||
{
|
||||
scale: ANIMATION.ZOOM_SCALE,
|
||||
duration: ANIMATION.ZOOM_DURATION,
|
||||
duration: ANIMATION.ZOOM_DURATION_INTRO,
|
||||
delay: zoomDelay,
|
||||
ease: ANIMATION.ZOOM_EASE,
|
||||
},
|
||||
@@ -118,7 +126,7 @@ export const useGalleryStore = defineStore("gallery", {
|
||||
setTimeout(() => {
|
||||
const { assets } = useAssets();
|
||||
assets.audio.whooshSmall.play(0.6);
|
||||
}, 500);
|
||||
}, 100);
|
||||
},
|
||||
|
||||
animateOutro() {
|
||||
@@ -129,7 +137,7 @@ export const useGalleryStore = defineStore("gallery", {
|
||||
|
||||
// Outro: Camera/zoom starts first (at 0), fade starts after with overlap
|
||||
const fadeDelay =
|
||||
ANIMATION.CAMERA_DURATION - ANIMATION.FADE_CAMERA_OVERLAP;
|
||||
ANIMATION.CAMERA_DURATION_OUTRO - ANIMATION.FADE_CAMERA_OVERLAP;
|
||||
|
||||
if (app.camera) {
|
||||
gsap.fromTo(
|
||||
@@ -143,7 +151,7 @@ export const useGalleryStore = defineStore("gallery", {
|
||||
x: ANIMATION.NDS_CAMERA_POSITION.x,
|
||||
y: ANIMATION.NDS_CAMERA_POSITION.y,
|
||||
z: ANIMATION.NDS_CAMERA_POSITION.z,
|
||||
duration: ANIMATION.CAMERA_DURATION,
|
||||
duration: ANIMATION.CAMERA_DURATION_OUTRO,
|
||||
delay: 0,
|
||||
ease: ANIMATION.ZOOM_EASE,
|
||||
},
|
||||
@@ -161,7 +169,7 @@ export const useGalleryStore = defineStore("gallery", {
|
||||
y: ANIMATION.NDS_CAMERA_ROTATION.y,
|
||||
z: ANIMATION.NDS_CAMERA_ROTATION.z,
|
||||
duration:
|
||||
ANIMATION.CAMERA_DURATION *
|
||||
ANIMATION.CAMERA_DURATION_OUTRO *
|
||||
(1 - ANIMATION.CAMERA_ROTATION_OVERLAP),
|
||||
delay: 0,
|
||||
ease: ANIMATION.ZOOM_EASE,
|
||||
@@ -173,7 +181,7 @@ export const useGalleryStore = defineStore("gallery", {
|
||||
{ scale: ANIMATION.ZOOM_SCALE },
|
||||
{
|
||||
scale: 1,
|
||||
duration: ANIMATION.ZOOM_DURATION,
|
||||
duration: ANIMATION.ZOOM_DURATION_OUTRO,
|
||||
delay: 0,
|
||||
ease: ANIMATION.ZOOM_EASE,
|
||||
},
|
||||
|
||||
@@ -67,7 +67,7 @@ export const useHomeStore = defineStore("home", {
|
||||
{ stage1Opacity: 0 },
|
||||
{
|
||||
stage1Opacity: 1,
|
||||
duration: 0.5,
|
||||
duration: 0.35,
|
||||
ease: "none",
|
||||
},
|
||||
0,
|
||||
@@ -80,7 +80,7 @@ export const useHomeStore = defineStore("home", {
|
||||
{ topScreenOpacity: 0 },
|
||||
{
|
||||
topScreenOpacity: 1,
|
||||
duration: 0.5,
|
||||
duration: 0.35,
|
||||
ease: "none",
|
||||
},
|
||||
0,
|
||||
@@ -90,10 +90,10 @@ export const useHomeStore = defineStore("home", {
|
||||
{ statusBarY: -20 },
|
||||
{
|
||||
statusBarY: 0,
|
||||
duration: 0.15,
|
||||
duration: 0.1,
|
||||
ease: "none",
|
||||
},
|
||||
0.35,
|
||||
0.15,
|
||||
);
|
||||
} else {
|
||||
this.intro.topScreenOpacity = 1;
|
||||
@@ -139,7 +139,7 @@ export const useHomeStore = defineStore("home", {
|
||||
{ stage2Opacity: 1 },
|
||||
{
|
||||
stage2Opacity: 0,
|
||||
duration: 0.16,
|
||||
duration: 0.12,
|
||||
ease: "none",
|
||||
},
|
||||
0,
|
||||
@@ -153,10 +153,10 @@ export const useHomeStore = defineStore("home", {
|
||||
{
|
||||
buttonOffsetY: -200,
|
||||
stage1Opacity: 0,
|
||||
duration: 0.4,
|
||||
duration: 0.3,
|
||||
ease: "none",
|
||||
},
|
||||
0.08,
|
||||
0.06,
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -36,7 +36,10 @@ export const useIntroStore = defineStore("intro", {
|
||||
() => {
|
||||
const now = new Date();
|
||||
const isBirthday = now.getMonth() === 3 && now.getDate() === 25;
|
||||
(isBirthday ? assets.audio.birthdayStartup : assets.audio.startUp).play();
|
||||
(isBirthday
|
||||
? assets.audio.birthdayStartup
|
||||
: assets.audio.startUp
|
||||
).play();
|
||||
},
|
||||
undefined,
|
||||
delay,
|
||||
@@ -83,7 +86,7 @@ export const useIntroStore = defineStore("intro", {
|
||||
.to(this.outro, {
|
||||
backgroundOpacity: 1,
|
||||
duration: 0.5,
|
||||
delay: 0.5,
|
||||
delay: 0.2,
|
||||
ease: "none",
|
||||
})
|
||||
.call(() => {
|
||||
|
||||
@@ -4,12 +4,21 @@ import type {
|
||||
} from "@nuxt/content";
|
||||
import gsap from "gsap";
|
||||
|
||||
export const useProjectsStore = defineStore("projects", {
|
||||
state: () => ({
|
||||
projects: [] as (Omit<
|
||||
type ProjectItem = Omit<
|
||||
ProjectsCollectionItem,
|
||||
keyof DataCollectionItemBase
|
||||
> & { id: string })[],
|
||||
> & {
|
||||
id: string;
|
||||
};
|
||||
export type Project = Omit<ProjectItem, "en" | "fr"> & {
|
||||
description: string;
|
||||
summary: string;
|
||||
tasks: string[];
|
||||
};
|
||||
|
||||
export const useProjectsStore = defineStore("projects", {
|
||||
state: () => ({
|
||||
projects: [] as Project[],
|
||||
currentProject: 0,
|
||||
loading: true,
|
||||
offsetX: 0,
|
||||
@@ -29,30 +38,34 @@ export const useProjectsStore = defineStore("projects", {
|
||||
|
||||
actions: {
|
||||
async loadProjects() {
|
||||
const { locale } = useI18n();
|
||||
this.loading = true;
|
||||
|
||||
const projects = await queryCollection("projects")
|
||||
const items = await queryCollection("projects")
|
||||
.order("order", "ASC")
|
||||
.select(
|
||||
"id",
|
||||
"order",
|
||||
"scope",
|
||||
"title",
|
||||
"link",
|
||||
"description",
|
||||
"summary",
|
||||
"technologies",
|
||||
"tasks",
|
||||
)
|
||||
.all();
|
||||
|
||||
if (!projects) throw "Cannot load projects";
|
||||
this.projects = projects.map((project) => ({
|
||||
...project,
|
||||
id: project.id
|
||||
.split("/")[2]!
|
||||
.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()),
|
||||
}));
|
||||
if (!items) throw "Cannot load projects";
|
||||
|
||||
this.projects = items.map((item) => {
|
||||
const slug = item.id.split("/")[2]!;
|
||||
const localeData = item[locale.value as "en"] ?? item.en;
|
||||
if (!localeData) {
|
||||
console.warn(`Missing '${locale.value}' for ${slug}`);
|
||||
}
|
||||
|
||||
const { en: _en, fr: _fr, ...meta } = item;
|
||||
|
||||
return {
|
||||
...meta,
|
||||
id: slug.replace(/-([a-z])/g, (_: string, letter: string) =>
|
||||
letter.toUpperCase(),
|
||||
),
|
||||
description: localeData.description ?? "",
|
||||
summary: localeData.summary ?? "",
|
||||
tasks: localeData.tasks ?? [],
|
||||
};
|
||||
});
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
@@ -185,21 +185,21 @@ export const useSettingsStore = defineStore("settings", {
|
||||
.fromTo(
|
||||
this.menuOffsets,
|
||||
{ 1: -48 },
|
||||
{ 1: 0, duration: 0.1, ease: "none" },
|
||||
{ 1: 0, duration: 0.143, ease: "none" },
|
||||
0.2,
|
||||
)
|
||||
.call(() => assets.audio.settingsMenuIntro.play(), undefined, 0.2)
|
||||
.fromTo(
|
||||
this.menuOffsets,
|
||||
{ 2: -48 },
|
||||
{ 2: 0, duration: 0.1, ease: "none" },
|
||||
0.3,
|
||||
{ 2: 0, duration: 0.143, ease: "none" },
|
||||
0.343,
|
||||
)
|
||||
.fromTo(
|
||||
this.menuOffsets,
|
||||
{ 3: -48 },
|
||||
{ 3: 0, duration: 0.1, ease: "none" },
|
||||
0.4,
|
||||
{ 3: 0, duration: 0.143, ease: "none" },
|
||||
0.486,
|
||||
)
|
||||
.call(() => {
|
||||
this.isIntro = false;
|
||||
@@ -232,27 +232,27 @@ export const useSettingsStore = defineStore("settings", {
|
||||
.fromTo(
|
||||
this.menuOffsets,
|
||||
{ 3: 0 },
|
||||
{ 3: -48, duration: 0.1, ease: "none" },
|
||||
{ 3: -48, duration: 0.143, ease: "none" },
|
||||
0,
|
||||
)
|
||||
.fromTo(
|
||||
this.menuOffsets,
|
||||
{ 2: 0 },
|
||||
{ 2: -48, duration: 0.1, ease: "none" },
|
||||
0.1,
|
||||
{ 2: -48, duration: 0.143, ease: "none" },
|
||||
0.143,
|
||||
)
|
||||
.fromTo(
|
||||
this.menuOffsets,
|
||||
{ 1: 0 },
|
||||
{ 1: -48, duration: 0.1, ease: "none" },
|
||||
0.2,
|
||||
{ 1: -48, duration: 0.143, ease: "none" },
|
||||
0.286,
|
||||
)
|
||||
// menus slide down
|
||||
.fromTo(
|
||||
this,
|
||||
{ menuYOffset: 0 },
|
||||
{ menuYOffset: 72, duration: 0.2, ease: "none" },
|
||||
0.3,
|
||||
0.429,
|
||||
)
|
||||
.call(() => {
|
||||
const app = useAppStore();
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { defineContentConfig, defineCollection } from "@nuxt/content";
|
||||
import { z } from "zod";
|
||||
|
||||
const localeSchema = z.object({
|
||||
description: z.string(),
|
||||
summary: z.string(),
|
||||
tasks: z.array(z.string()),
|
||||
});
|
||||
|
||||
export default defineContentConfig({
|
||||
collections: {
|
||||
projects: defineCollection({
|
||||
@@ -11,10 +17,9 @@ export default defineContentConfig({
|
||||
scope: z.enum(["hobby", "work"]),
|
||||
title: z.string(),
|
||||
link: z.url(),
|
||||
description: z.string(),
|
||||
summary: z.string(),
|
||||
technologies: z.array(z.string()),
|
||||
tasks: z.array(z.string()),
|
||||
en: localeSchema,
|
||||
fr: localeSchema,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -3,12 +3,21 @@ scope: work
|
||||
title: Biobleud
|
||||
link: https://biobleud.fr/
|
||||
|
||||
description: Agri-food company
|
||||
summary: Temporary assignments
|
||||
technologies:
|
||||
- VBA
|
||||
|
||||
en:
|
||||
description: Agri-food company
|
||||
summary: Temporary assignments
|
||||
tasks:
|
||||
- Developing Excel macros\nin VBA for ERP system\nimplementation
|
||||
- Understanding client\nneeds
|
||||
- Documentation
|
||||
- "Developing Excel macros\\nin VBA for ERP system\\nimplementation"
|
||||
- "Understanding client\\nneeds"
|
||||
- "Documentation"
|
||||
|
||||
fr:
|
||||
description: Entreprise agroalimentaire
|
||||
summary: Missions temporaires
|
||||
tasks:
|
||||
- "Développement de macros\\nExcel en VBA pour\\nun ERP"
|
||||
- "Compréhension des\\nbesoins du client"
|
||||
- "Documentation"
|
||||
|
||||
@@ -3,13 +3,22 @@ scope: hobby
|
||||
title: LBF Bot
|
||||
link: https://git.pihkaal.me/lbf-bot
|
||||
|
||||
description: For a gaming group
|
||||
summary: Custom Discord bot
|
||||
technologies:
|
||||
- Node
|
||||
- TypeScript
|
||||
|
||||
en:
|
||||
description: For a gaming group
|
||||
summary: Custom Discord bot
|
||||
tasks:
|
||||
- Made for a gaming group
|
||||
- Deployed on VPS
|
||||
- Understanding client\nneeds
|
||||
- "Made for a gaming group"
|
||||
- "Deployed on VPS"
|
||||
- "Understanding client\\nneeds"
|
||||
|
||||
fr:
|
||||
description: Pour un groupe de gaming
|
||||
summary: Bot Discord personnalisé
|
||||
tasks:
|
||||
- "Créé pour un groupe\\nautour d'un jeu"
|
||||
- "Déployé sur VPS"
|
||||
- "Compréhension des\\nbesoins du client"
|
||||
|
||||
@@ -3,13 +3,20 @@ scope: hobby
|
||||
title: lilou.cat
|
||||
link: https://lilou.cat
|
||||
|
||||
description: Lilou <3
|
||||
summary: Lilou's website
|
||||
|
||||
technologies:
|
||||
- HTML
|
||||
- Go
|
||||
|
||||
en:
|
||||
description: Lilou <3
|
||||
summary: Lilou's website
|
||||
tasks:
|
||||
- Originally made for fun\nto celebrate my cat Lilou
|
||||
- Now preserved in her\nmemory
|
||||
- "Originally made for fun\\nto celebrate my cat Lilou"
|
||||
- "Now preserved in her\\nmemory"
|
||||
|
||||
fr:
|
||||
description: Lilou <3
|
||||
summary: Le site de Lilou
|
||||
tasks:
|
||||
- "Créé pour célébrer\\nmon chat Lilou"
|
||||
- "Désormais préservé\\nen sa mémoire"
|
||||
|
||||
@@ -3,13 +3,20 @@ scope: hobby
|
||||
title: pihkaal.me
|
||||
link: https://pihkaal.me
|
||||
|
||||
description: Portfolio and contact
|
||||
summary: My personnal website
|
||||
|
||||
technologies:
|
||||
- Nuxt
|
||||
- TypeScript
|
||||
|
||||
en:
|
||||
description: Portfolio and contact
|
||||
summary: My personal website
|
||||
tasks:
|
||||
- The website you are\ncurrently on!
|
||||
- Recreation of the Nintendo\nDS because it was my first\never console
|
||||
- "The website you are\\ncurrently on!"
|
||||
- "Recreation of the Nintendo\\nDS because it was my first\\never console"
|
||||
|
||||
fr:
|
||||
description: Portfolio et contact
|
||||
summary: Mon site personnel
|
||||
tasks:
|
||||
- "Le site sur lequel\\nvous êtes !"
|
||||
- "Recréation de la Nintendo\\nDS car c'était ma première\\nconsole"
|
||||
|
||||
@@ -3,8 +3,6 @@ scope: hobby
|
||||
title: Raylib Spdrns
|
||||
link: https://git.pihkaal.me/raylib-speedruns
|
||||
|
||||
description: Awesome video game library
|
||||
summary: Raylib Speedruns
|
||||
technologies:
|
||||
- C
|
||||
- C#
|
||||
@@ -13,6 +11,16 @@ technologies:
|
||||
- Rust
|
||||
- Asm x86_64
|
||||
|
||||
en:
|
||||
description: Awesome video game library
|
||||
summary: Raylib Speedruns
|
||||
tasks:
|
||||
- Simple Raylib setups in\nmultiple languages
|
||||
- Inspired by Tsoding
|
||||
- "Simple Raylib setups in\\nmultiple languages"
|
||||
- "Inspired by Tsoding"
|
||||
|
||||
fr:
|
||||
description: Super bibliothèque
|
||||
summary: Raylib Speedruns
|
||||
tasks:
|
||||
- "Exemples d'utilisation de\\nRaylib en plusieurs\\nlangages"
|
||||
- "Inspiré par Tsoding"
|
||||
|
||||
@@ -3,15 +3,25 @@ scope: work
|
||||
title: S3PWeb
|
||||
link: https://s3pweb.com
|
||||
|
||||
description: The Transport Data Aggregator
|
||||
summary: Apprenticeship
|
||||
technologies:
|
||||
- Node
|
||||
- StencilJS
|
||||
- TypeScript
|
||||
|
||||
en:
|
||||
description: The Transport Data Aggregator
|
||||
summary: Apprenticeship
|
||||
tasks:
|
||||
- Automatized incidents\naggregation to Jira
|
||||
- Web based map editor
|
||||
- Chrome extension to\nvisualize Eramba assets
|
||||
- Documentation
|
||||
- "Automatized incidents\\naggregation to Jira"
|
||||
- "Web based map editor"
|
||||
- "Chrome extension to\\nvisualize Eramba assets"
|
||||
- "Documentation"
|
||||
|
||||
fr:
|
||||
description: L'agrégateur des dataa transport
|
||||
summary: Alternance
|
||||
tasks:
|
||||
- "Agrégation automatisée\\nd'incidents vers Jira"
|
||||
- "Éditeur de carte web"
|
||||
- "Extension Chrome pour\\nles assets Eramba"
|
||||
- "Documentation"
|
||||
|
||||
@@ -3,13 +3,22 @@ scope: hobby
|
||||
title: Simple QR
|
||||
link: https://simple-qr.com
|
||||
|
||||
description: Concise website and API
|
||||
summary: QR code generator
|
||||
technologies:
|
||||
- Nuxt
|
||||
- TypeScript
|
||||
|
||||
en:
|
||||
description: Concise website and API
|
||||
summary: QR code generator
|
||||
tasks:
|
||||
- Easy to use
|
||||
- Large choice of logos
|
||||
- Straightforward API
|
||||
- "Easy to use"
|
||||
- "Large choice of logos"
|
||||
- "Straightforward API"
|
||||
|
||||
fr:
|
||||
description: Site web et API concis
|
||||
summary: Générateur de QR code
|
||||
tasks:
|
||||
- "Facile à utiliser"
|
||||
- "Grand choix de logos"
|
||||
- "API simple d'utilisation"
|
||||
|
||||
@@ -3,14 +3,23 @@ scope: hobby
|
||||
title: tlock
|
||||
link: https://git.pihkaal.me/tlock
|
||||
|
||||
description: For Hyprland ricing
|
||||
summary: Terminal based clock
|
||||
technologies:
|
||||
- Rust
|
||||
|
||||
en:
|
||||
description: For Hyprland ricing
|
||||
summary: Terminal based clock
|
||||
tasks:
|
||||
- Fully customizable
|
||||
- Animated
|
||||
- Cross-platform
|
||||
- |
|
||||
Multiple modes: clock,\nchronometer and timer
|
||||
- "Fully customizable"
|
||||
- "Animated"
|
||||
- "Cross-platform"
|
||||
- "Multiple modes: clock,\\nchronometer and timer"
|
||||
|
||||
fr:
|
||||
description: Pour le ricing Hyprland
|
||||
summary: Horloge pour le terminal
|
||||
tasks:
|
||||
- "Entièrement\\npersonnalisable"
|
||||
- "Animée"
|
||||
- "Multi-plateforme"
|
||||
- "Plusieurs modes : horloge,\\nchronomètre et minuteur"
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"screens": {
|
||||
"home": "Home",
|
||||
"contact": "Contact",
|
||||
"projects": "Projects",
|
||||
"settings": "Settings",
|
||||
"gallery": "Gallery",
|
||||
"achievements": "Achievements",
|
||||
"credits": "Credits"
|
||||
},
|
||||
"lagModal": {
|
||||
"title": "Performance issues detected",
|
||||
"body": "Your device seems to be struggling with 3D rendering. Switch to 2D mode for a smoother experience.",
|
||||
@@ -56,7 +65,6 @@
|
||||
"2048_score_512": "Reach the 512 tile\nin 2048",
|
||||
"taptap_score_20": "Score 20 points\nin TapTap",
|
||||
"settings_color_try_all": "Try all colors",
|
||||
"settings_language_try_all": "Try all languages",
|
||||
"settings_visit_all": "Visit all settings\nsubmenus",
|
||||
"contact_36_notifications": "Trigger 36\nnotifications"
|
||||
},
|
||||
@@ -75,7 +83,7 @@
|
||||
"description": "Change system settings here. Select\nthe settings you'd like to change.",
|
||||
"options": {
|
||||
"title": "Options",
|
||||
"description": "Change other settings.",
|
||||
"description": "Change rendering mode,\nlanguage, and play 2048.",
|
||||
"renderingMode": {
|
||||
"title": "Rendering",
|
||||
"description": "Change the app rendering mode\nbetween 2D and 3D.",
|
||||
@@ -89,7 +97,8 @@
|
||||
"language": {
|
||||
"title": "Language",
|
||||
"description": "Select the language to use.",
|
||||
"confirmation": "Language set to English."
|
||||
"confirmation": "Language set to English.",
|
||||
"unavailable": "This language is not\navailable yet."
|
||||
},
|
||||
"2048": {
|
||||
"title": "2048",
|
||||
@@ -129,7 +138,7 @@
|
||||
},
|
||||
"user": {
|
||||
"title": "User",
|
||||
"description": "Enter user informations.",
|
||||
"description": "Change color, view my birthday\nand name, and play Snake.",
|
||||
"color": {
|
||||
"title": "Color",
|
||||
"description": "Select your favorite color.",
|
||||
@@ -180,7 +189,7 @@
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"title": "Choose a Chat Room to join.",
|
||||
"title": "",
|
||||
"actions": {
|
||||
"open": "Open",
|
||||
"copy": "Copy",
|
||||
@@ -199,7 +208,7 @@
|
||||
"backToHome": "Back to Home"
|
||||
},
|
||||
"projects": {
|
||||
"linkConformationPopup": {
|
||||
"linkConfirmationPopup": {
|
||||
"yes": "yes",
|
||||
"no": "no",
|
||||
"text": "Open {url}?"
|
||||
|
||||
@@ -1 +1,221 @@
|
||||
{}
|
||||
{
|
||||
"screens": {
|
||||
"home": "Accueil",
|
||||
"contact": "Contact",
|
||||
"projects": "Projets",
|
||||
"settings": "Paramètres",
|
||||
"gallery": "Galerie",
|
||||
"achievements": "Succès",
|
||||
"credits": "Crédits"
|
||||
},
|
||||
"lagModal": {
|
||||
"title": "Problèmes de performances détectés",
|
||||
"body": "Votre appareil semble avoir du mal avec le rendu 3D. Passez en mode 2D pour une expérience plus fluide.",
|
||||
"keep3d": "Garder la 3D",
|
||||
"switch2d": "Passer en 2D"
|
||||
},
|
||||
"common": {
|
||||
"cancel": "Annuler",
|
||||
"confirm": "Confirmer",
|
||||
"quit": "Quitter",
|
||||
"start": "Démarrer",
|
||||
"restart": "Relancer",
|
||||
"reset": "Réinitialiser",
|
||||
"select": "Sélectionner",
|
||||
"goBack": "Retour",
|
||||
"yes": "Oui",
|
||||
"no": "Non"
|
||||
},
|
||||
"achievementsScreen": {
|
||||
"title": "Succès"
|
||||
},
|
||||
"creditsScreen": {
|
||||
"title": "Crédits",
|
||||
"model3d": {
|
||||
"label": "Modèle 3D par",
|
||||
"author": "Cianon",
|
||||
"url": "skfb.ly/6ZDvQ - CC BY 4.0"
|
||||
},
|
||||
"css2d": {
|
||||
"label": "CSS 2D par",
|
||||
"author": "A. Radevich",
|
||||
"url": "codepen.io/aradevich/pen/mdRYzyJ"
|
||||
},
|
||||
"nintendo": {
|
||||
"label": "UI & effets sonores par",
|
||||
"author": "Nintendo",
|
||||
"url": "nintendo.com - © Nintendo"
|
||||
},
|
||||
"defectds": {
|
||||
"label": "Sons système NDS extraits par",
|
||||
"author": "defectDS",
|
||||
"url": "adiumxtras.com"
|
||||
}
|
||||
},
|
||||
"achievements": {
|
||||
"boot": "Démarrer le système",
|
||||
"projects_visit": "Visiter la section\nProjets",
|
||||
"projects_view_5": "Voir 5 projets",
|
||||
"projects_open_link": "Ouvrir le lien\nd'un projet",
|
||||
"gallery_visit": "Visiter la galerie\nphoto",
|
||||
"contact_visit": "Visiter la section\nContact",
|
||||
"contact_git_visit": "Visiter mon profil Git",
|
||||
"settings_color_change": "Changer la couleur\ndu système",
|
||||
"snake_score_25": "Marquer 25 points\nà Snake",
|
||||
"2048_score_512": "Atteindre la tuile 512\nà 2048",
|
||||
"taptap_score_20": "Marquer 20 points\nà TapTap",
|
||||
"settings_color_try_all": "Essayer toutes les\ncouleurs",
|
||||
"settings_visit_all": "Visiter tous les\nparamètres",
|
||||
"contact_36_notifications": "Déclencher 36\nnotifications"
|
||||
},
|
||||
"intro": {
|
||||
"copyright": "AVERTISSEMENT - COPYRIGHT",
|
||||
"text": "CECI EST UNE RECRÉATION NON COMMERCIALE\nFAITE PAR UN FAN. NON AFFILIÉ NI\nAPPROUVÉ PAR NINTENDO.\nNINTENDO DS EST UNE MARQUE DÉPOSÉE\nDE NINTENDO CO., LTD.",
|
||||
"hint": "Touchez l'écran tactile pour continuer."
|
||||
},
|
||||
"home": {
|
||||
"projectsAndExperiences": "Projets et\nExpériences",
|
||||
"greeting": "Bienvenue sur mon site !",
|
||||
"photoGallery": "Galerie Photo"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Paramètres",
|
||||
"description": "Changer les paramètres. Choisissez\nle paramètre à modifier.",
|
||||
"options": {
|
||||
"title": "Options",
|
||||
"description": "Changer le mode de rendu,\nla langue, et jouer au 2048.",
|
||||
"renderingMode": {
|
||||
"title": "Rendu",
|
||||
"description": "Changer le mode de rendu\nentre 2D et 3D.",
|
||||
"3dMode": "Mode 3D",
|
||||
"2dMode": "Mode 2D",
|
||||
"3dDescription": "Expérience 3D complète avec\nle modèle interactif.\nIdéal pour les appareils puissants.",
|
||||
"2dDescription": "Expérience légère et épurée.\nPlus rapide et moins demandante.\nRecommandé si le mode 3D rame.",
|
||||
"confirmation3d": "Mode de rendu réglé sur 3D",
|
||||
"confirmation2d": "Mode de rendu réglé sur 2D"
|
||||
},
|
||||
"language": {
|
||||
"title": "Langue",
|
||||
"description": "Sélectionner la langue que\nvous voulez utiliser.",
|
||||
"confirmation": "La langue est réglée sur Français.",
|
||||
"unavailable": "Cette langue n'est pas\nencore disponible."
|
||||
},
|
||||
"2048": {
|
||||
"title": "2048",
|
||||
"description": "Glissez ou utilisez les flèches\npour fusionner les tuiles.\nAtteignez 2048 !",
|
||||
"quitConfirmation": "Quitter la partie ?\nVotre score est sauvegardé.",
|
||||
"restartConfirmation": "Recommencer la partie ?",
|
||||
"gameOver": "Perdu !\nRecommencer ?",
|
||||
"score": "Score",
|
||||
"highScore": "Meilleur"
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"title": "Horloge",
|
||||
"description": "Changer la date, l'heure et les\nparamètres de succès.",
|
||||
"achievements": {
|
||||
"title": "Succès",
|
||||
"description": "Gérer vos succès.",
|
||||
"resetButton": "Réinitialiser les succès",
|
||||
"resetConfirmation": "Réinitialiser tous les succès ?",
|
||||
"viewAll": "Tout voir",
|
||||
"obtained": "Obtenus",
|
||||
"total": "Total"
|
||||
},
|
||||
"date": {
|
||||
"title": "Date",
|
||||
"description": "Date d'aujourd'hui.",
|
||||
"month": "Mois",
|
||||
"day": "Jour",
|
||||
"year": "Année"
|
||||
},
|
||||
"time": {
|
||||
"title": "Heure",
|
||||
"description": "Heure actuelle.",
|
||||
"hour": "Heure",
|
||||
"minute": "Minute"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"title": "Utilisateur",
|
||||
"description": "Changer la couleur, voir mon nom et\nmon anniversaire, et jouer à Snake.",
|
||||
"color": {
|
||||
"title": "Couleur",
|
||||
"description": "Sélectionnez votre\ncouleur favorite.",
|
||||
"confirmation": "La couleur a été sauvegardée."
|
||||
},
|
||||
"birthday": {
|
||||
"title": "Anniversaire",
|
||||
"description": "Ma date d'anniversaire.",
|
||||
"month": "Mois",
|
||||
"day": "Jour",
|
||||
"year": "Année",
|
||||
"confirmation": {
|
||||
"today": "Oui, c'est aujourd'hui !",
|
||||
"future": "N'oubliez pas de me le\nsouhaiter dans {days} jours !"
|
||||
}
|
||||
},
|
||||
"userName": {
|
||||
"title": "Nom d'utilisateur",
|
||||
"description": "Mon pseudo et mon prénom.",
|
||||
"userName": "Nom d'utilisateur",
|
||||
"firstName": "Prénom"
|
||||
},
|
||||
"snake": {
|
||||
"title": "Snake",
|
||||
"description": "Glissez ou utilisez les flèches\npour vous déplacer.\nNe vous mordez pas la queue !",
|
||||
"score": "Score : {score}",
|
||||
"best": "Meilleur : {best}",
|
||||
"startPrompt": "\n\n\n Appuyez sur icon_a\n pour démarrer",
|
||||
"quitConfirmation": "Quitter la partie ?",
|
||||
"restartConfirmation": "Recommencer la partie ?"
|
||||
}
|
||||
},
|
||||
"touchScreen": {
|
||||
"title": "Écran Tactile",
|
||||
"description": "Calibrage et jeux de l'écran tactile.",
|
||||
"tapTap": {
|
||||
"title": "TapTap",
|
||||
"description": "Tapez les cercles avant qu'ils\nne disparaissent !",
|
||||
"startPrompt": "Appuyez sur icon_a pour démarrer.",
|
||||
"score": "Score : {score}",
|
||||
"best": "Meilleur : {best}",
|
||||
"gameOver": "Partie terminée !",
|
||||
"finalScore": "Score final : {score}",
|
||||
"newRecord": "Nouveau record !",
|
||||
"quitConfirmation": "Quitter la partie ?",
|
||||
"restartConfirmation": "Recommencer la partie ?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"title": "",
|
||||
"actions": {
|
||||
"open": "Ouvrir",
|
||||
"copy": "Copier",
|
||||
"git": "Page Git",
|
||||
"email": "E-mail",
|
||||
"linkedin": "Lien LinkedIn",
|
||||
"cv": "CV"
|
||||
},
|
||||
"copiedToClipboard": "{item} copié dans le presse-papiers",
|
||||
"openUrl": "Ouvrir {url} ?",
|
||||
"opened": "{item} ouvert"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Galerie de Pihkaal",
|
||||
"description": "J'ai débuté en mars 2025. J'adore photographier les plantes, les insectes et les arachnides.",
|
||||
"backToHome": "Retour à l'accueil"
|
||||
},
|
||||
"projects": {
|
||||
"linkConfirmationPopup": {
|
||||
"yes": "oui",
|
||||
"no": "non",
|
||||
"text": "Ouvrir {url} ?"
|
||||
}
|
||||
},
|
||||
"loadingScreen": {
|
||||
"loading": "Chargement...",
|
||||
"clickToStart": "Cliquez pour démarrer"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,15 +37,14 @@ export default defineNuxtConfig({
|
||||
{ property: "og:image:type", content: "image/png" },
|
||||
{ property: "og:image:width", content: "1200" },
|
||||
{ property: "og:image:height", content: "630" },
|
||||
{ property: "og:image:alt", content: "3D Nintendo DS home screen from pihkaal.me" },
|
||||
{
|
||||
property: "og:image:alt",
|
||||
content: "3D Nintendo DS home screen from pihkaal.me",
|
||||
},
|
||||
{ property: "og:url", content: URL },
|
||||
{ property: "og:site_name", content: TITLE },
|
||||
{ property: "og:locale", content: "en-US" },
|
||||
{ property: "og:locale:alternate", content: "de-DE" },
|
||||
{ property: "og:locale:alternate", content: "fr-FR" },
|
||||
{ property: "og:locale:alternate", content: "es-ES" },
|
||||
{ property: "og:locale:alternate", content: "it-IT" },
|
||||
{ property: "og:locale:alternate", content: "ja-JP" },
|
||||
{ name: "twitter:card", content: "summary_large_image" },
|
||||
{ name: "twitter:title", content: TITLE },
|
||||
{ name: "twitter:description", content: DESCRIPTION },
|
||||
@@ -99,8 +98,11 @@ export default defineNuxtConfig({
|
||||
{ code: "ja", language: "ja-JP", name: "日本語", file: "ja.json" },
|
||||
],
|
||||
defaultLocale: "en",
|
||||
// TODO: put back to true
|
||||
detectBrowserLanguage: false,
|
||||
detectBrowserLanguage: {
|
||||
useCookie: true,
|
||||
cookieKey: "i18n_redirected",
|
||||
redirectOn: "root",
|
||||
},
|
||||
},
|
||||
image: {
|
||||
quality: 80,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.6 MiB |
Reference in New Issue
Block a user