BREAKING CHANGE: vanilla ts three scene setup

This commit is contained in:
2025-12-13 15:47:30 +01:00
parent 1631e3206a
commit a5dba4b652
158 changed files with 610 additions and 14258 deletions

View File

@@ -1,34 +0,0 @@
name: CI
on:
push:
branches: [main, 3d-nds]
pull_request:
branches: [main, 3d-nds]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: pnpm setup
uses: pnpm/action-setup@v4
with:
version: 10
- name: node setup
uses: actions/setup-node@v4
with:
node-version: 25
cache: "pnpm"
- name: install
run: pnpm install --frozen-lockfile
- name: lint
run: pnpm lint
- name: build
run: pnpm build

47
.gitignore vendored
View File

@@ -1,33 +1,26 @@
# generated
public/images/projects
# temporary
__old
# ESlint
.eslintcache
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
OLD
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Misc
.DS_Store
.fleet
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
# Local env files
.env
.env.*
!.env.example
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

2
.prettierignore Normal file
View File

@@ -0,0 +1,2 @@
OLD
.test

View File

@@ -1,75 +0,0 @@
# Nuxt Minimal Starter
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

View File

@@ -1,3 +0,0 @@
<template>
<NuxtPage />
</template>

View File

@@ -1,32 +0,0 @@
@font-face {
font-family: "NDS7";
src: url("/assets/fonts/nds-7px.woff2") format("woff2");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "NDS10";
src: url("/assets/fonts/nds-10px.woff2") format("woff2");
font-weight: normal;
font-style: normal;
}
/* NOTE: woff2 version of this font doesn't work */
@font-face {
font-family: "Pokemon DP Pro";
src: url("/assets/fonts/pokemon-dp-pro.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
image-rendering: pixelated;
image-rendering: crisp-edges;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

View File

@@ -1,69 +0,0 @@
<script setup lang="ts">
import CORNER_IMAGE from "/assets/images/home/bottom-screen/buttons/corner.webp";
const props = withDefaults(
defineProps<{
rect: [x: number, y: number, width: number, height: number];
opacity?: number;
}>(),
{
opacity: 1,
},
);
const [cornerImage] = useImages(CORNER_IMAGE);
const ANIMATION_SPEED = 0.25;
let [currentX, currentY, currentWidth, currentHeight] = props.rect;
useRender((ctx) => {
const [targetX, targetY, targetWidth, targetHeight] = props.rect;
const dx = targetX - currentX;
const dy = targetY - currentY;
const dw = targetWidth - currentWidth;
const dh = targetHeight - currentHeight;
if (
Math.abs(dx) < 0.5 &&
Math.abs(dy) < 0.5 &&
Math.abs(dw) < 0.5 &&
Math.abs(dh) < 0.5
) {
[currentX, currentY, currentWidth, currentHeight] = props.rect;
} else {
currentX += dx * ANIMATION_SPEED;
currentY += dy * ANIMATION_SPEED;
currentWidth += dw * ANIMATION_SPEED;
currentHeight += dh * ANIMATION_SPEED;
}
const x = Math.floor(currentX);
const y = Math.floor(currentY);
const w = Math.floor(currentWidth);
const h = Math.floor(currentHeight);
ctx.globalAlpha = props.opacity;
ctx.drawImage(cornerImage!, x, y);
ctx.save();
ctx.scale(-1, 1);
ctx.drawImage(cornerImage!, -(x + w), y);
ctx.restore();
ctx.save();
ctx.scale(1, -1);
ctx.drawImage(cornerImage!, x, -(y + h));
ctx.restore();
ctx.save();
ctx.scale(-1, -1);
ctx.drawImage(cornerImage!, -(x + w), -(y + h));
ctx.restore();
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,24 +0,0 @@
<script setup lang="ts">
import HOME_BACKGROUND_IMAGE from "/assets/images/home/bottom-screen/background.webp";
import CONTACT_BACKGROUND_IMAGE from "/assets/images/contact/bottom-screen/background.webp";
const store = useContactStore();
const [homeBackgroundImage, contactBackgroundImage] = useImages(
HOME_BACKGROUND_IMAGE,
CONTACT_BACKGROUND_IMAGE,
);
useRender((ctx) => {
ctx.drawImage(homeBackgroundImage!, 0, 0);
ctx.globalAlpha = store.isIntro
? store.intro.stage2Opacity
: store.outro.stage3Opacity;
ctx.drawImage(contactBackgroundImage!, 0, 0);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,39 +0,0 @@
<script setup lang="ts">
import TOP_BAR_IMAGE from "/assets/images/contact/bottom-screen/top-bar.webp";
import BOTTOM_BAR_IMAGE from "/assets/images/contact/bottom-screen/bottom-bar.webp";
import BOTTOM_BAR_OK_IMAGE from "/assets/images/contact/bottom-screen/ok-button.webp";
const props = defineProps<{
okLabel: "Copy" | "Open";
}>();
const store = useContactStore();
const [topBarImage, bottomBarImage, bottomBarOkImage] = useImages(
TOP_BAR_IMAGE,
BOTTOM_BAR_IMAGE,
BOTTOM_BAR_OK_IMAGE,
);
useRender((ctx) => {
ctx.globalAlpha = store.isIntro
? store.intro.stage3Opacity
: store.outro.stage2Opacity;
// top bar
ctx.drawImage(topBarImage!, 0, store.isIntro ? store.intro.topBarY : 0);
// bottom bar
ctx.translate(0, store.isIntro ? store.intro.bottomBarY : SCREEN_HEIGHT - 24);
ctx.drawImage(bottomBarImage!, 0, 0);
ctx.drawImage(bottomBarOkImage!, 144, 4);
ctx.font = "10px NDS10";
ctx.fillStyle = "#000000";
ctx.fillText(props.okLabel, 144 + 35, 4 + 13);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,87 +0,0 @@
<script setup lang="ts">
import Background from "./Background.vue";
import Buttons from "./Buttons.vue";
import ButtonSelector from "~/components/Common/ButtonSelector.vue";
import Bars from "./Bars.vue";
const store = useContactStore();
const ACTIONS = {
github: ["Open", "Github profile", "https://github.com/pihkaal"],
email: ["Copy", "Email", "hello@pihkaal.me"],
website: ["Copy", "Website link", "https://pihkaal.me"],
cv: ["Open", "CV", "https://pihkaal.me/cv"],
} as const satisfies Record<
string,
[action: "Copy" | "Open", verb: string, content: string]
>;
const { selectedButton, selectorPosition } = useButtonNavigation({
buttons: {
github: [26, 27, 202, 42],
email: [26, 59, 202, 42],
website: [26, 91, 202, 42],
cv: [26, 123, 202, 42],
},
navigation: {
github: {
down: "email",
},
email: {
up: "github",
down: "website",
},
website: {
up: "email",
down: "cv",
},
cv: {
up: "website",
},
},
initialButton: "github",
onButtonClick: async (button) => {
actionateButton(button);
},
});
const actionateButton = async (button: (typeof selectedButton)["value"]) => {
const [action, verb, content] = ACTIONS[button];
if (action === "Copy") {
try {
await navigator.clipboard.writeText(content);
store.pushNotification(`${verb} copied to clipboard`);
} catch (error) {
console.error("Failed to copy to clipboard:", error);
}
} else {
await navigateTo(content, { open: { target: "_blank " } });
store.pushNotification(`${verb} opened`);
}
};
const QUIT_BUTTON: Rect = [31, 172, 80, 18];
const OK_BUTTON: Rect = [144, 172, 80, 18];
useScreenClick((x, y) => {
if (rectContains(QUIT_BUTTON, [x, y])) {
store.animateOutro();
} else if (rectContains(OK_BUTTON, [x, y])) {
actionateButton(selectedButton.value);
}
});
</script>
<template>
<Background />
<Buttons />
<ButtonSelector
:rect="selectorPosition"
:opacity="
store.isIntro ? store.intro.stage3Opacity : store.outro.stage1Opacity
"
/>
<Bars :ok-label="ACTIONS[selectedButton][0]" />
</template>

View File

@@ -1,18 +0,0 @@
<script setup lang="ts">
import BUTTONS_IMAGE from "/assets/images/contact/bottom-screen/buttons.webp";
const store = useContactStore();
const [buttonsImage] = useImages(BUTTONS_IMAGE);
useRender((ctx) => {
ctx.globalAlpha = store.isIntro
? store.intro.stage3Opacity
: store.outro.stage1Opacity;
ctx.drawImage(buttonsImage!, 31, 32);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,24 +0,0 @@
<script setup lang="ts">
import HOME_BACKGROUND_IMAGE from "/assets/images/home/top-screen/background.webp";
import CONTACT_BACKGROUND_IMAGE from "/assets/images/contact/top-screen/background.webp";
const store = useContactStore();
const [homeBackgroundImage, contactBackgroundImage] = useImages(
HOME_BACKGROUND_IMAGE,
CONTACT_BACKGROUND_IMAGE,
);
useRender((ctx) => {
ctx.drawImage(homeBackgroundImage!, 0, 0);
ctx.globalAlpha = store.isIntro
? store.intro.stage2Opacity
: store.outro.stage3Opacity;
ctx.drawImage(contactBackgroundImage!, 0, 0);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,27 +0,0 @@
<script setup lang="ts">
import BACKGROUND_IMAGE from "/assets/images/contact/top-screen/left-bar.webp";
import THINGS_IMAGE from "/assets/images/contact/top-screen/left-bar-things.webp";
const store = useContactStore();
const [backgroundImage, thingsImage] = useImages(
BACKGROUND_IMAGE,
THINGS_IMAGE,
);
useRender((ctx) => {
ctx.globalAlpha = store.isIntro
? store.intro.stage1Opacity
: store.outro.stage2Opacity;
ctx.drawImage(backgroundImage!, 0, 0);
ctx.globalAlpha = store.isIntro
? store.intro.stage3Opacity
: store.outro.stage1Opacity;
ctx.drawImage(thingsImage!, 0, 0);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,80 +0,0 @@
<script setup lang="ts">
import NOTIFICATION_IMAGE from "/assets/images/contact/bottom-screen/notification.webp";
import TITLE_IMAGE from "/assets/images/contact/top-screen/title.webp";
// text color:
const store = useContactStore();
const [notificationImage, titleImage] = useImages(
NOTIFICATION_IMAGE,
TITLE_IMAGE,
);
useRender((ctx) => {
ctx.globalAlpha = store.outro.stage2Opacity;
ctx.font = "10px NDS10";
// notifications
for (let i = store.notifications.length - 1; i >= 0; i--) {
const index = store.notifications.length - 1 - i;
const y = 169 - 24 * index + store.notificationsYOffset;
if (y < -24) break;
ctx.drawImage(notificationImage!, 21, y);
const content = store.notifications[i]!;
ctx.fillStyle = content.includes("opened") ? "#00fbba" : "#e3f300";
ctx.fillText(store.notifications[i]!, 27, y + 15);
}
// title
ctx.globalAlpha = store.isIntro
? store.intro.stage1Opacity
: store.outro.stage2Opacity;
ctx.drawImage(
titleImage!,
21,
store.isIntro
? store.intro.titleY
: 169 - 24 * store.notifications.length + store.notificationsYOffset,
);
// notifications count (left bar)
const MAX = 36;
const MAX_VISIBLE = 8;
let visibleNotifications = Math.min(store.notifications.length, MAX_VISIBLE);
const extraActive =
store.notificationsYOffset > 0 && store.notifications.length > MAX_VISIBLE;
if (extraActive) {
visibleNotifications += 1;
}
ctx.fillStyle = "#415969";
for (let i = 0; i < visibleNotifications; i++) {
ctx.fillRect(3, 161 - i * 4, 12, 2);
}
ctx.fillStyle = "#b2c3db";
const startY = 161 - visibleNotifications * 4;
const top = MAX - MAX_VISIBLE - (extraActive ? 1 : 0);
for (
let i = 0;
i < store.notifications.length - visibleNotifications && i < top;
i++
) {
if (i === top - 1) {
ctx.fillRect(7, startY - i * 4, 4, 2);
} else if (i === top - 2) {
ctx.fillRect(5, startY - i * 4, 8, 2);
} else {
ctx.fillRect(3, startY - i * 4, 12, 2);
}
}
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,20 +0,0 @@
<script setup lang="ts">
import Background from "./Background.vue";
import LeftBar from "./LeftBar.vue";
import Notifications from "./Notifications.vue";
const store = useContactStore();
onMounted(() => {
store.$reset();
store.animateIntro();
});
</script>
<template>
<Background />
<LeftBar />
<Notifications />
</template>

View File

@@ -1,17 +0,0 @@
<script setup lang="ts">
import BACKGROUND_IMAGE from "/assets/images/home/bottom-screen/background.webp";
const store = useHomeStore();
const app = useAppStore();
const [backgroundImage] = useImages(BACKGROUND_IMAGE);
useRender((ctx) => {
ctx.globalAlpha = app.booted ? 1 : store.intro.stage1Opacity;
ctx.drawImage(backgroundImage!, 0, 0);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,10 +0,0 @@
<script setup lang="ts">
import Background from "./Background.vue";
import Buttons from "./Buttons/Buttons.vue";
</script>
<template>
<Background />
<Buttons />
</template>

View File

@@ -1,82 +0,0 @@
<script setup lang="ts">
import GameButton from "./GameButton.vue";
import ContactButton from "./ContactButton.vue";
import DownloadPlayButton from "./DownloadPlayButton.vue";
import SettingsButton from "./SettingsButton.vue";
import Selector from "~/components/Common/ButtonSelector.vue";
const store = useHomeStore();
const { selectedButton, selectorPosition } = useButtonNavigation({
buttons: {
projects: [31, 23, 193, 49],
contact: [31, 71, 97, 49],
downloadPlay: [127, 71, 97, 49],
settings: [112, 167, 31, 26],
},
initialButton: "projects",
onButtonClick: (button) => {
if (button === "downloadPlay") throw new Error("Not implemented");
store.animateOutro(button);
},
navigation: {
projects: {
down: "last",
left: "contact",
right: "downloadPlay",
horizontalMode: "preview",
},
contact: {
up: "projects",
right: "downloadPlay",
down: "settings",
},
downloadPlay: {
up: "projects",
left: "contact",
down: "settings",
},
settings: {
up: "last",
},
},
});
const getButtonOffset = (button: (typeof selectedButton)["value"]) => {
if (selectedButton.value === button) return store.outro.buttonOffsetY;
return 0;
};
const getOpacity = (button?: (typeof selectedButton)["value"]) => {
if (store.isIntro) return store.intro.stage1Opacity;
if (selectedButton.value === button) return 1;
if (store.isOutro) return store.outro.stage1Opacity;
return 1;
};
</script>
<template>
<GameButton
:x="33"
:y="25 + getButtonOffset('projects')"
:opacity="getOpacity('projects')"
/>
<DownloadPlayButton
:x="128"
:y="72 + getButtonOffset('downloadPlay')"
:opacity="getOpacity('downloadPlay')"
/>
<ContactButton
:x="32"
:y="72 + getButtonOffset('contact')"
:opacity="getOpacity('contact')"
/>
<SettingsButton
:x="117"
:y="170 + getButtonOffset('settings')"
:opacity="getOpacity('settings')"
/>
<Selector :rect="selectorPosition" :opacity="getOpacity()" />
</template>

View File

@@ -1,20 +0,0 @@
<script setup lang="ts">
import BUTTON_IMAGE from "/assets/images/home/bottom-screen/buttons/contact.webp";
const props = defineProps<{
x: number;
y: number;
opacity: number;
}>();
const [buttonImage] = useImages(BUTTON_IMAGE);
useRender((ctx) => {
ctx.globalAlpha = props.opacity;
ctx.drawImage(buttonImage!, props.x, props.y);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,20 +0,0 @@
<script setup lang="ts">
import BUTTON_IMAGE from "/assets/images/home/bottom-screen/buttons/downloadPlay.webp";
const props = defineProps<{
x: number;
y: number;
opacity: number;
}>();
const [buttonImage] = useImages(BUTTON_IMAGE);
useRender((ctx) => {
ctx.globalAlpha = props.opacity;
ctx.drawImage(buttonImage!, props.x, props.y);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,20 +0,0 @@
<script setup lang="ts">
import BUTTON_IMAGE from "/assets/images/home/bottom-screen/buttons/game.webp";
const props = defineProps<{
x: number;
y: number;
opacity: number;
}>();
const [buttonImage] = useImages(BUTTON_IMAGE);
useRender((ctx) => {
ctx.globalAlpha = props.opacity;
ctx.drawImage(buttonImage!, props.x, props.y);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,20 +0,0 @@
<script setup lang="ts">
import BUTTON_IMAGE from "/assets/images/home/bottom-screen/buttons/settings.webp";
const props = defineProps<{
x: number;
y: number;
opacity: number;
}>();
const [buttonImage] = useImages(BUTTON_IMAGE);
useRender((ctx) => {
ctx.globalAlpha = props.opacity;
ctx.drawImage(buttonImage!, props.x, props.y);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,17 +0,0 @@
<script setup lang="ts">
import BACKGROUND_IMAGE from "/assets/images/home/top-screen/background.webp";
const store = useHomeStore();
const app = useAppStore();
const [backgroundImage] = useImages(BACKGROUND_IMAGE);
useRender((ctx) => {
ctx.globalAlpha = app.booted ? 1 : store.intro.stage1Opacity;
ctx.drawImage(backgroundImage!, 0, 0);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,92 +0,0 @@
<script setup lang="ts">
import CALENDAR_IMAGE from "/assets/images/home/top-screen/calendar/calendar.webp";
import LAST_ROW_IMAGE from "/assets/images/home/top-screen/calendar/last-row.webp";
import DAY_SELECTOR_IMAGE from "/assets/images/home/top-screen/calendar/day-selector.webp";
// NOTE: calendar background is handled by TopScreenBackground
const store = useHomeStore();
const [calendarImage, lastRowImage, daySelectorImage] = useImages(
CALENDAR_IMAGE,
LAST_ROW_IMAGE,
DAY_SELECTOR_IMAGE,
);
useRender((ctx) => {
ctx.fillStyle = "black";
ctx.font = "7px NDS7";
const CALENDAR_COLS = 7;
const CALENDAR_ROWS = 5;
const CALENDAR_LEFT = 128;
const CALENDAR_TOP = 64;
ctx.fillStyle = "#343434";
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth();
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
ctx.globalAlpha = store.isIntro
? store.intro.stage1Opacity
: store.isOutro && store.outro.animateTop
? store.outro.stage1Opacity
: 1;
ctx.drawImage(calendarImage!, CALENDAR_LEFT - 3, CALENDAR_TOP - 33);
const extraRow = CALENDAR_COLS * CALENDAR_ROWS - daysInMonth - firstDay < 0;
if (extraRow) {
ctx.drawImage(lastRowImage!, CALENDAR_LEFT - 3, CALENDAR_TOP + 79);
}
ctx.globalAlpha = store.isIntro
? store.intro.stage1Opacity
: store.isOutro && store.outro.animateTop
? store.outro.stage2Opacity
: 1;
for (let col = 0; col < CALENDAR_ROWS + (extraRow ? 1 : 0); col += 1) {
for (let row = 0; row < CALENDAR_COLS; row += 1) {
const cellIndex = col * CALENDAR_COLS + row;
const day = cellIndex - firstDay + 1;
if (day > 0 && day <= daysInMonth) {
const dayText = day.toString();
const { actualBoundingBoxRight: width } = ctx.measureText(dayText);
const cellLeft = CALENDAR_LEFT + row * 16;
const cellTop = CALENDAR_TOP + col * 16;
if (now.getDate() === day) {
ctx.drawImage(daySelectorImage!, cellLeft, cellTop);
}
ctx.fillText(
dayText,
cellLeft + Math.floor((15 - width) / 2),
cellTop + 11,
);
}
}
}
ctx.fillStyle = "black";
ctx.font = "10px NDS10";
ctx.letterSpacing = "2px";
const timeText = `${month}/${year}`;
const { actualBoundingBoxRight: width } = ctx.measureText(timeText);
ctx.fillText(
timeText,
CALENDAR_LEFT + Math.floor((111 - width) / 2),
CALENDAR_TOP - 20,
);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,100 +0,0 @@
<script setup lang="ts">
import CLOCK_IMAGE from "/assets/images/home/top-screen/clock.webp";
const CENTER_X = 63;
const CENTER_Y = 95;
const store = useHomeStore();
const [clockImage] = useImages(CLOCK_IMAGE);
function drawLine(
ctx: CanvasRenderingContext2D,
x0: number,
y0: number,
x1: number,
y1: number,
width: number,
) {
const dx = Math.abs(x1 - x0);
const dy = Math.abs(y1 - y0);
const sx = x0 < x1 ? 1 : -1;
const sy = y0 < y1 ? 1 : -1;
let err = dx - dy;
const drawThickPixel = (x: number, y: number) => {
const isVertical = dy > dx;
if (width === 1) {
ctx.fillRect(x, y, 1, 1);
} else if (isVertical) {
const offset = Math.floor((width - 1) / 2);
ctx.fillRect(x - offset, y, width, 1);
} else {
const offset = Math.floor((width - 1) / 2);
ctx.fillRect(x, y - offset, 1, width);
}
};
while (true) {
drawThickPixel(x0, y0);
if (x0 === x1 && y0 === y1) break;
const e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
useRender((ctx) => {
ctx.globalAlpha = store.isIntro
? store.intro.stage1Opacity
: store.isOutro && store.outro.animateTop
? store.outro.stage1Opacity
: 1;
ctx.drawImage(clockImage!, 13, 45);
ctx.globalAlpha = store.isIntro
? store.intro.stage1Opacity
: store.isOutro && store.outro.animateTop
? store.outro.stage2Opacity
: 1;
const now = new Date();
const renderHand = (
value: number,
color: string,
length: number,
width: number,
) => {
const angle = value * Math.PI * 2 - Math.PI / 2;
const endX = Math.round(CENTER_X + Math.cos(angle) * length);
const endY = Math.round(CENTER_Y + Math.sin(angle) * length);
ctx.fillStyle = color;
drawLine(ctx, CENTER_X, CENTER_Y, endX, endY, width);
};
renderHand(now.getMinutes() / 60, "#797979", 30, 2);
renderHand(
now.getHours() / 12 + now.getMinutes() / 60 / 12,
"#797979",
23,
2,
);
renderHand(now.getSeconds() / 60, "#49db8a", 35, 2);
ctx.fillStyle = "#494949";
ctx.fillRect(CENTER_X - 2, CENTER_Y - 2, 5, 5);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,62 +0,0 @@
<script setup lang="ts">
import STATUS_BAR_IMAGE from "/assets/images/home/top-screen/status-bar/status-bar.webp";
import GBA_DISPLAY_IMAGE from "/assets/images/home/top-screen/status-bar/gba-display.webp";
import STARTUP_MODE_IMAGE from "/assets/images/home/top-screen/status-bar/startup-mode.webp";
import BATTERY_IMAGE from "/assets/images/home/top-screen/status-bar/battery.webp";
const store = useHomeStore();
const [statusBarImage, gbaDisplayImage, startupModeImage, batteryImage] =
useImages(
STATUS_BAR_IMAGE,
GBA_DISPLAY_IMAGE,
STARTUP_MODE_IMAGE,
BATTERY_IMAGE,
);
useRender((ctx) => {
const TEXT_Y = 11;
ctx.translate(0, store.isIntro ? store.intro.statusBarY : 0);
ctx.globalAlpha =
store.isOutro && store.outro.animateTop ? store.outro.stage2Opacity : 1;
ctx.drawImage(statusBarImage!, 0, 0);
ctx.fillStyle = "#ffffff";
ctx.font = "7px NDS7";
// username
ctx.fillText("pihkaal", 3, TEXT_Y);
// time + date
const fillNumberCell = (value: number, cellX: number, offset: number) => {
const text = value.toFixed().padStart(2, "0");
const { actualBoundingBoxRight: width } = ctx.measureText(text);
const x = cellX * 16;
ctx.fillText(text, Math.floor(x + offset + (16 - width) / 2), TEXT_Y);
};
const now = new Date();
fillNumberCell(now.getHours(), 9, 1);
if (Math.floor(now.getMilliseconds() / 500) % 2 == 0) {
ctx.fillText(":", 159, TEXT_Y);
}
fillNumberCell(now.getMinutes(), 10, -1);
fillNumberCell(now.getDate(), 11, 1);
ctx.fillText("/", 190, TEXT_Y);
fillNumberCell(now.getMonth() + 1, 12, -1);
// icons
ctx.drawImage(gbaDisplayImage!, 210, 2);
ctx.drawImage(startupModeImage!, 226, 2);
ctx.drawImage(batteryImage!, 242, 4);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,20 +0,0 @@
<script setup lang="ts">
import Background from "./Background.vue";
import Calendar from "./Calendar.vue";
import Clock from "./Clock.vue";
import StatusBar from "./StatusBar.vue";
const store = useHomeStore();
onMounted(() => {
store.$reset();
store.animateIntro();
});
</script>
<template>
<Background />
<Calendar />
<Clock />
<StatusBar />
</template>

View File

@@ -1,12 +0,0 @@
<script setup lang="ts">
import BACKGROUND_IMAGE from "~/assets/images/projects/bottom-screen/background.webp";
const [backgroundImage] = useImages(BACKGROUND_IMAGE);
useRender((ctx) => {
ctx.drawImage(backgroundImage!, 0, 0);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,17 +0,0 @@
<script setup lang="ts">
import Background from "./Background.vue";
import Buttons from "./Buttons.vue";
const store = useProjectsStore();
onMounted(async () => {
store.$reset();
await store.loadProjects();
});
</script>
<template>
<Background />
<Buttons v-if="!store.loading" />
</template>

View File

@@ -1,56 +0,0 @@
<script setup lang="ts">
const store = useProjectsStore();
const PREV_BUTTON: Point = [36, 100];
const QUIT_BUTTON: Point = [88, 156];
const LINK_BUTTON: Point = [168, 156];
const NEXT_BUTTON: Point = [220, 100];
const CLICK_RADIUS = 22;
const circleContains = (
[cx, cy]: Point,
[x, y]: Point,
radius: number,
): boolean => Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2)) < radius;
useScreenClick((x, y) => {
const project = store.projects[store.currentProject];
if (circleContains(PREV_BUTTON, [x, y], CLICK_RADIUS)) {
store.scrollProjects("left");
} else if (circleContains(NEXT_BUTTON, [x, y], CLICK_RADIUS)) {
store.scrollProjects("right");
} else if (circleContains(QUIT_BUTTON, [x, y], CLICK_RADIUS)) {
throw new Error("quit");
} else if (
circleContains(LINK_BUTTON, [x, y], CLICK_RADIUS) &&
project?.link
) {
// TODO: show confirmation popup before opening the link, like "you are about to navigate to [...]"
store.visitProject();
}
});
useScreenMouseWheel((dy) => {
if (dy > 0) {
store.scrollProjects("right");
} else if (dy < 0) {
store.scrollProjects("left");
}
});
useKeyDown((key) => {
switch (key) {
case "ArrowLeft":
store.scrollProjects("left");
break;
case "ArrowRight":
store.scrollProjects("right");
break;
}
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,13 +0,0 @@
<script setup lang="ts">
import BACKGROUND_IMAGE from "/assets/images/projects/top-screen/background.webp";
const [backgroundImage] = useImages(BACKGROUND_IMAGE);
useRender((ctx) => {
ctx.drawImage(backgroundImage!, 0, 0);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,134 +0,0 @@
<script setup lang="ts">
import BACKGROUND_IMAGE from "~/assets/images/projects/top-screen/background.webp";
const store = useProjectsStore();
const [backgroundImage, ...projectImages] = useImages(
BACKGROUND_IMAGE,
...store.projects.map((project) => `/images/projects/${project.id}.webp`),
);
const drawTextWithShadow = (
ctx: CanvasRenderingContext2D,
color: "white" | "black",
text: string,
x: number,
y: number,
) => {
ctx.fillStyle = color === "white" ? "#505050" : "#a8b8b8";
ctx.fillText(text, x + 1, y + 0);
ctx.fillText(text, x + 1, y + 1);
ctx.fillText(text, x + 0, y + 1);
ctx.fillStyle = color === "white" ? "#f8f8f8" : "#101820";
ctx.fillText(text, x, y);
};
const drawTextWithShadow2Lines = (
ctx: CanvasRenderingContext2D,
text: string,
x: number,
y: number,
maxWidth: number,
line1Color: "white" | "black",
line2Color: "white" | "black",
) => {
const { actualBoundingBoxRight: textWidth } = ctx.measureText(text);
if (textWidth <= maxWidth) {
drawTextWithShadow(ctx, line1Color, text, x, y);
return;
}
const words = text.split(" ");
let firstLine = "";
let secondLine = "";
for (let i = 0; i < words.length; i++) {
const testLine = firstLine + (firstLine ? " " : "") + words[i];
const { actualBoundingBoxRight: testWidth } = ctx.measureText(testLine);
if (testWidth > maxWidth && firstLine) {
secondLine = words.slice(i).join(" ");
break;
}
firstLine = testLine;
}
drawTextWithShadow(ctx, line1Color, firstLine, x, y);
drawTextWithShadow(ctx, line2Color, secondLine, x, y + 16);
};
useRender((ctx) => {
ctx.drawImage(backgroundImage!, 0, 0);
ctx.textBaseline = "hanging";
ctx.font = "16px Pokemon DP Pro";
const project = store.projects[store.currentProject];
if (!project) return;
// image
const projectImage = projectImages[store.currentProject]!;
ctx.drawImage(
projectImage,
Math.floor(52 - projectImage.width / 2),
Math.floor(104 - projectImage.height / 2),
);
// text
drawTextWithShadow(ctx, "white", project.title.toUpperCase(), 23, 25);
drawTextWithShadow(ctx, "black", project.scope, 12, 41);
drawTextWithShadow2Lines(
ctx,
project.description,
8,
161,
90,
"white",
"black",
);
const { actualBoundingBoxRight: textWidth } = ctx.measureText(
project.summary,
);
drawTextWithShadow(
ctx,
"black",
project.summary,
Math.floor(185 - textWidth / 2),
19,
);
let textY = 35;
for (let i = 0; i < project.tasks.length; i += 1) {
const lines = project.tasks[i]!.split("\\n");
ctx.fillStyle = i % 2 === 0 ? "#6870d8" : "#8890f8";
ctx.fillRect(106, textY - 1, 150, lines.length * 16);
ctx.fillStyle = i % 2 === 0 ? "#8890f8" : "#b0b8d0";
ctx.fillRect(105, textY - 1, 1, lines.length * 16);
for (let j = 0; j < lines.length; j += 1) {
drawTextWithShadow(ctx, "white", lines[j]!, 118, textY);
textY += 16;
}
}
drawTextWithShadow2Lines(
ctx,
project.technologies.join(", "),
111,
161,
145,
"black",
"black",
);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,12 +0,0 @@
<script setup lang="ts">
import Background from "./Background.vue";
import Project from "./Project.vue";
const store = useProjectsStore();
</script>
<template>
<Background />
<Project v-if="!store.loading" />
</template>

View File

@@ -1,127 +0,0 @@
<script lang="ts" setup>
const canvas = useTemplateRef("canvas");
const updateCallbacks = new Set<UpdateCallback>();
const renderCallbacks = new Set<RenderCallback>();
const screenClickCallbacks = new Set<ScreenClickCallback>();
const screenMouseWheelCallbacks = new Set<ScreenMouseWheelCallback>();
let ctx: CanvasRenderingContext2D | null = null;
let animationFrameId: number | null = null;
let lastFrameTime = 0;
let lastRealFrameTime = 0;
const registerUpdateCallback = (callback: UpdateCallback) => {
updateCallbacks.add(callback);
return () => updateCallbacks.delete(callback);
};
const registerRenderCallback = (callback: RenderCallback) => {
renderCallbacks.add(callback);
return () => renderCallbacks.delete(callback);
};
const registerScreenClickCallback = (callback: ScreenClickCallback) => {
screenClickCallbacks.add(callback);
return () => screenClickCallbacks.delete(callback);
};
const registerScreenMouseWheelCallback = (
callback: ScreenMouseWheelCallback,
) => {
screenMouseWheelCallbacks.add(callback);
return () => screenMouseWheelCallbacks.delete(callback);
};
const handleCanvasClick = (event: MouseEvent) => {
if (!canvas.value) return;
const rect = canvas.value.getBoundingClientRect();
const scaleX = SCREEN_WIDTH / rect.width;
const scaleY = SCREEN_HEIGHT / rect.height;
const x = (event.clientX - rect.left) * scaleX;
const y = (event.clientY - rect.top) * scaleY;
for (const callback of screenClickCallbacks) {
callback(x, y);
}
};
const handleCanvasWheel = (event: WheelEvent) => {
for (const callback of screenMouseWheelCallbacks) {
callback(event.deltaY, event.deltaX);
}
};
const renderFrame = (timestamp: number) => {
if (!ctx) return;
const deltaTime = timestamp - lastFrameTime;
lastFrameTime = timestamp;
const start = Date.now();
// update
for (const callback of updateCallbacks) {
callback(deltaTime, lastRealFrameTime);
}
// render
ctx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
for (const callback of renderCallbacks) {
ctx.save();
callback(ctx);
ctx.restore();
}
lastRealFrameTime = Date.now() - start;
animationFrameId = requestAnimationFrame(renderFrame);
};
onMounted(() => {
if (!canvas.value) throw new Error("Missing canvas");
ctx = canvas.value.getContext("2d");
if (!ctx) throw new Error("Missing 2d context");
provide("registerUpdateCallback", registerUpdateCallback);
provide("registerRenderCallback", registerRenderCallback);
provide("registerScreenClickCallback", registerScreenClickCallback);
provide("registerScreenMouseWheelCallback", registerScreenMouseWheelCallback);
canvas.value.addEventListener("click", handleCanvasClick);
canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true });
animationFrameId = requestAnimationFrame(renderFrame);
});
onUnmounted(() => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
}
if (canvas.value) {
canvas.value.removeEventListener("click", handleCanvasClick);
canvas.value.removeEventListener("wheel", handleCanvasWheel);
}
});
</script>
<template>
<canvas
ref="canvas"
:width="SCREEN_WIDTH"
:height="SCREEN_HEIGHT"
:style="{
margin: '0',
border: '1px solid red',
}"
/>
<slot v-if="canvas" />
</template>

View File

@@ -1,21 +0,0 @@
<script setup lang="ts">
import BACKGROUND_IMAGE from "/assets/images/home/bottom-screen/background.webp";
import TOP_BAR_IMAGE from "/assets/images/settings/bottom-screen/top-bar.webp";
import BOTTOM_BAR_IMAGE from "/assets/images/settings/bottom-screen/bottom-bar.webp";
const [backgroundImage, topBarImage, bottomBarImage] = useImages(
BACKGROUND_IMAGE,
TOP_BAR_IMAGE,
BOTTOM_BAR_IMAGE,
);
useRender((ctx) => {
ctx.drawImage(backgroundImage!, 0, 0);
ctx.drawImage(topBarImage!, 0, 0);
ctx.drawImage(bottomBarImage!, 0, 168);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,9 +0,0 @@
<script setup lang="ts">
import Background from "./Background.vue";
import Menus from "./Menus/Menus.vue";
</script>
<template>
<Background />
<Menus />
</template>

View File

@@ -1,66 +0,0 @@
<script setup lang="ts">
import MENU_IMAGE from "/assets/images/settings/top-screen/clock/clock.webp";
import MENU_ACTIVE_IMAGE from "/assets/images/settings/top-screen/clock/clock-active.webp";
import MENU_DISABLED_IMAGE from "/assets/images/settings/top-screen/clock/clock-disabled.png";
import ALARM_IMAGE from "/assets/images/settings/top-screen/clock/alarm.webp";
import TIME_IMAGE from "/assets/images/settings/top-screen/clock/time.webp";
import DATE_IMAGE from "/assets/images/settings/top-screen/clock/date.webp";
const props = defineProps<{
x: number;
y: number;
}>();
const settingsStore = useSettingsStore();
const [
menuImage,
menuActiveImage,
menuDisabledImage,
alarmImage,
timeImage,
dateImage,
] = useImages(
MENU_IMAGE,
MENU_ACTIVE_IMAGE,
MENU_DISABLED_IMAGE,
ALARM_IMAGE,
TIME_IMAGE,
DATE_IMAGE,
);
const isOpen = computed(() => settingsStore.isMenuOpen("clock"));
const isAnyOtherMenuOpen = computed(() =>
settingsStore.isAnyOtherMenuOpen("clock"),
);
const animation = useMenuAnimation("clock", isOpen);
useRender((ctx) => {
ctx.translate(props.x, props.y);
if (isOpen.value || animation.playing) {
ctx.drawImage(
timeImage!,
48 - animation.stage2Offset,
-48 + animation.stage1Offset,
);
ctx.drawImage(
dateImage!,
0,
-96 + animation.stage2Offset + animation.stage1Offset,
);
ctx.drawImage(alarmImage!, 0, -48 + animation.stage1Offset);
ctx.drawImage(menuActiveImage!, 0, 0);
} else if (isAnyOtherMenuOpen.value) {
ctx.drawImage(menuDisabledImage!, 0, 0);
} else {
ctx.drawImage(menuImage!, 0, 0);
}
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,184 +0,0 @@
<script setup lang="ts">
import OptionsMenu from "./Options/Menu.vue";
import OptionsStartUp from "./Options/StartUp.vue";
import OptionsLanguage from "./Options/Language.vue";
import OptionsGbaMode from "./Options/GbaMode.vue";
import ClockMenu from "./Clock/Menu.vue";
import UserMenu from "./User/Menu.vue";
import TouchScreenMenu from "./TouchScreen/Menu.vue";
import Selector from "~/components/Common/ButtonSelector.vue";
const settingsStore = useSettingsStore();
const { selectedButton: selected, selectorPosition } = useButtonNavigation({
buttons: {
options: [31, 119, 49, 49],
optionsLanguage: [31, 71, 49, 49],
optionsGbaMode: [79, 71, 49, 49],
optionsStartUp: [31, 23, 49, 49],
clock: [79, 119, 49, 49],
clockAlarm: [79, 71, 49, 49],
clockTime: [127, 71, 49, 49],
clockDate: [79, 23, 49, 49],
user: [127, 119, 49, 49],
userBirthday: [79, 71, 49, 49],
userName: [127, 71, 49, 49],
userMessage: [175, 71, 49, 49],
userColor: [127, 23, 49, 49],
touchScreen: [175, 119, 49, 49],
},
initialButton: "options",
onButtonClick: (buttonName: string) => {
if (isSubmenu(buttonName)) {
router.push({
query: {
menu: buttonName,
},
});
}
},
navigation: {
options: {
right: "clock",
up: "optionsLanguage",
},
optionsLanguage: {
down: "options",
up: "optionsStartUp",
right: "optionsGbaMode",
},
optionsGbaMode: {
down: "options",
left: "optionsLanguage",
up: "optionsStartUp",
},
optionsStartUp: {
right: "optionsGbaMode",
down: "optionsLanguage",
},
clock: {
left: "options",
right: "user",
up: "clockAlarm",
},
clockAlarm: {
down: "clock",
up: "clockDate",
right: "clockTime",
},
clockTime: {
down: "clock",
left: "clockAlarm",
up: "clockDate",
},
clockDate: {
right: "clockTime",
down: "clockAlarm",
},
user: {
left: "clock",
right: "touchScreen",
up: "userName",
},
userBirthday: {
down: "user",
up: "userColor",
right: "userName",
},
userName: {
down: "user",
left: "userBirthday",
right: "userMessage",
up: "userColor",
},
userMessage: {
down: "user",
left: "userName",
up: "userColor",
},
userColor: {
left: "userBirthday",
right: "userMessage",
down: "userName",
},
touchScreen: {
left: "user",
},
},
});
const isSubmenu = (buttonName: string) => {
return (
/^(options|clock|user|touchScreen)[A-Z]/.test(buttonName) &&
!["options", "clock", "user", "touchScreen"].includes(buttonName)
);
};
const router = useRouter();
onBeforeRouteUpdate((to, from) => {
const fromMenu = from.query.menu?.toString();
const toMenu = to.query.menu?.toString();
if (!fromMenu && toMenu) {
settingsStore.setActiveMenu(selected.value);
} else if (fromMenu && !toMenu) {
if (fromMenu === "options" || fromMenu === "clock" || fromMenu === "user") {
selected.value = fromMenu;
settingsStore.setActiveMenu(null);
} else {
throw new Error("Unreachable");
}
} else if (fromMenu && toMenu) {
if (toMenu === "options" || toMenu === "clock" || toMenu === "user") {
settingsStore.setCurrentSubMenu(null);
settingsStore.setActiveMenu(selected.value);
} else {
settingsStore.setCurrentSubMenu(toMenu);
}
}
});
watch(
selected,
(newSelected) => {
if (settingsStore.currentSubMenu === null) {
if (isSubmenu(newSelected)) {
router.push({
query: {
menu: newSelected.split(/[A-Z]/)[0],
},
});
} else {
router.push({ query: { menu: undefined } });
}
}
},
{ immediate: true },
);
const viewComponents: Record<string, Component> = {
optionsStartUp: OptionsStartUp,
optionsLanguage: OptionsLanguage,
optionsGbaMode: OptionsGbaMode,
};
</script>
<template>
<template v-if="!settingsStore.currentSubMenu">
<OptionsMenu :x="33" :y="121" />
<ClockMenu :x="81" :y="121" />
<UserMenu :x="129" :y="121" />
<TouchScreenMenu :x="177" :y="121" :opacity="1" />
<Selector :rect="selectorPosition" :opacity="1" />
</template>
<component :is="viewComponents[settingsStore.currentSubMenu]" v-else />
</template>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
useRender((ctx) => {
ctx.font = "10px NDS10";
ctx.fillStyle = "#000000";
ctx.fillText("GBA Mode", 10, 20);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
useRender((ctx) => {
ctx.font = "10px NDS10";
ctx.fillStyle = "#000000";
ctx.fillText("Language", 10, 20);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,66 +0,0 @@
<script setup lang="ts">
import MENU_IMAGE from "/assets/images/settings/top-screen/options/options.webp";
import MENU_ACTIVE_IMAGE from "/assets/images/settings/top-screen/options/options-active.png";
import MENU_DISABLED_IMAGE from "/assets/images/settings/top-screen/options/options-disabled.png";
import GBA_MODE_IMAGE from "/assets/images/settings/top-screen/options/gba-mode.webp";
import LANGUAGE_IMAGE from "/assets/images/settings/top-screen/options/language.webp";
import START_UP_IMAGE from "/assets/images/settings/top-screen/options/start-up.webp";
const props = defineProps<{
x: number;
y: number;
}>();
const settingsStore = useSettingsStore();
const [
menuImage,
menuActiveImage,
menuDisabledImage,
gbaModeImage,
languageImage,
startUpImage,
] = useImages(
MENU_IMAGE,
MENU_ACTIVE_IMAGE,
MENU_DISABLED_IMAGE,
GBA_MODE_IMAGE,
LANGUAGE_IMAGE,
START_UP_IMAGE,
);
const isOpen = computed(() => settingsStore.isMenuOpen("options"));
const isAnyOtherMenuOpen = computed(() =>
settingsStore.isAnyOtherMenuOpen("options"),
);
const animation = useMenuAnimation("options", isOpen);
useRender((ctx) => {
ctx.translate(props.x, props.y);
if (isOpen.value || animation.playing) {
ctx.drawImage(languageImage!, 0, -48 + animation.stage1Offset);
ctx.drawImage(
gbaModeImage!,
48 - animation.stage2Offset,
-48 + animation.stage1Offset,
);
ctx.drawImage(
startUpImage!,
0,
-96 + animation.stage2Offset + animation.stage1Offset,
);
ctx.drawImage(menuActiveImage!, 0, 0);
} else if (isAnyOtherMenuOpen.value) {
ctx.drawImage(menuDisabledImage!, 0, 0);
} else {
ctx.drawImage(menuImage!, 0, 0);
}
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
useRender((ctx) => {
ctx.font = "10px NDS10";
ctx.fillStyle = "#000000";
ctx.fillText("Startup", 10, 20);
});
defineOptions({
render: () => null,
});
</script>

View File

@@ -1,32 +0,0 @@
<script setup lang="ts">
import MENU_IMAGE from "/assets/images/settings/top-screen/touch_screen/touch-screen.webp";
import MENU_DISABLED_IMAGE from "/assets/images/settings/top-screen/touch_screen/touch-screen-disabled.png";
const props = defineProps<{
x: number;
y: number;
}>();
const settingsStore = useSettingsStore();
const [menuImage, menuDisabledImage] = useImages(
MENU_IMAGE,
MENU_DISABLED_IMAGE,
);
const isAnyOtherMenuOpen = computed(() =>
settingsStore.isAnyOtherMenuOpen("touchScreen"),
);
useRender((ctx) => {
if (isAnyOtherMenuOpen.value) {
ctx.drawImage(menuDisabledImage!, props.x, props.y);
} else {
ctx.drawImage(menuImage!, props.x, props.y);
}
});
defineOptions({
render: () => null,
});
</script>

Some files were not shown because too many files have changed in this diff Show More