feat(nds): add pressed state for all buttons

This commit is contained in:
2026-02-11 23:33:26 +01:00
parent 305ef81083
commit 2137abf424
57 changed files with 413 additions and 369 deletions

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
const { onRender, onClick } = useScreen(); const { onRender, onClick, onMouseDown } = useScreen();
const store = useAchievementsScreen(); const store = useAchievementsScreen();
const achievementsStore = useAchievementsStore(); const achievementsStore = useAchievementsStore();
const { assets } = useAssets(); const { assets } = useAssets();
@@ -8,6 +8,22 @@ const QUIT_SIZE = assets.images.achievements.quit.rect.width;
const QUIT_X = Math.floor(LOGICAL_WIDTH / 2 - QUIT_SIZE / 2); const QUIT_X = Math.floor(LOGICAL_WIDTH / 2 - QUIT_SIZE / 2);
const QUIT_Y = 135; const QUIT_Y = 135;
const quitPressed = ref(false);
const handleMouseUp = () => {
quitPressed.value = false;
};
onMounted(() => document.addEventListener("mouseup", handleMouseUp));
onUnmounted(() => document.removeEventListener("mouseup", handleMouseUp));
onMouseDown((x, y) => {
if (store.isIntro || store.isOutro) return;
if (rectContains([QUIT_X, QUIT_Y, QUIT_SIZE, QUIT_SIZE], [x, y])) {
quitPressed.value = true;
}
});
useKeyDown(({ key, repeated }) => { useKeyDown(({ key, repeated }) => {
if (store.isIntro || store.isOutro || repeated) return; if (store.isIntro || store.isOutro || repeated) return;
@@ -76,7 +92,10 @@ onRender((ctx) => {
: store.isOutro : store.isOutro
? store.outro.stage2Opacity ? store.outro.stage2Opacity
: 1; : 1;
assets.images.achievements.quit.draw(ctx, QUIT_X, QUIT_Y); (quitPressed.value
? assets.images.achievements.quitPressed
: assets.images.achievements.quit
).draw(ctx, QUIT_X, QUIT_Y);
}); });
defineOptions({ defineOptions({

View File

@@ -13,11 +13,10 @@ const emit = defineEmits<{
activateB: []; activateB: [];
}>(); }>();
const { onRender, onClick } = useScreen(); const { onRender, onClick, onMouseDown } = useScreen();
const { assets } = useAssets();
const BUTTON_WIDTH = assets.images.common.button.rect.width; const BUTTON_WIDTH = 80;
const BUTTON_HEIGHT = assets.images.common.button.rect.height; const BUTTON_HEIGHT = 18;
const B_BUTTON: Rect = [31, 172, BUTTON_WIDTH, BUTTON_HEIGHT]; const B_BUTTON: Rect = [31, 172, BUTTON_WIDTH, BUTTON_HEIGHT];
const A_BUTTON: Rect = [144, 172, BUTTON_WIDTH, BUTTON_HEIGHT]; const A_BUTTON: Rect = [144, 172, BUTTON_WIDTH, BUTTON_HEIGHT];
@@ -31,6 +30,9 @@ let aButtonOffsetY = 0;
let displayedBLabel = props.bLabel; let displayedBLabel = props.bLabel;
let displayedALabel = props.aLabel; let displayedALabel = props.aLabel;
let bButtonPressed = false;
let aButtonPressed = false;
const animateLabelChange = ( const animateLabelChange = (
setter: (v: number) => void, setter: (v: number) => void,
setLabel: (label: string) => void, setLabel: (label: string) => void,
@@ -80,25 +82,24 @@ onRender((ctx) => {
ctx.font = "10px NDS10"; ctx.font = "10px NDS10";
ctx.fillStyle = "#010101"; ctx.fillStyle = "#010101";
ctx.translate(0, props.yOffset); ctx.translate(0, 172 + props.yOffset);
const drawButton = ( drawButton(
icon: string, ctx,
text: string, `${ICONS.B} ${displayedBLabel}`,
x: number, 31,
buttonOffsetY: number, bButtonOffsetY,
) => { BUTTON_WIDTH,
ctx.save(); bButtonPressed,
ctx.translate(0, buttonOffsetY); );
assets.images.common.button.draw(ctx, x, 172); drawButton(
ctx,
const label = `${icon} ${text}`; `${ICONS.A} ${displayedALabel}`,
fillTextHCentered(ctx, label, x, 185, BUTTON_WIDTH); 144,
ctx.restore(); aButtonOffsetY,
}; BUTTON_WIDTH,
aButtonPressed,
drawButton(ICONS.B, displayedBLabel, 31, bButtonOffsetY); );
drawButton(ICONS.A, displayedALabel, 144, aButtonOffsetY);
}, 60); }, 60);
onClick((x, y) => { onClick((x, y) => {
@@ -110,6 +111,20 @@ onClick((x, y) => {
} }
}); });
onMouseDown((x, y) => {
if (props.yOffset !== 0) return;
if (rectContains(B_BUTTON, [x, y])) {
bButtonPressed = true;
} else if (rectContains(A_BUTTON, [x, y])) {
aButtonPressed = true;
}
});
useMouseUp(() => {
bButtonPressed = false;
aButtonPressed = false;
});
useKeyDown(({ key, repeated }) => { useKeyDown(({ key, repeated }) => {
if (props.yOffset !== 0 || repeated) return; if (props.yOffset !== 0 || repeated) return;
switch (key) { switch (key) {

View File

@@ -1,106 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import Background from "./Background.vue"; import Background from "./Background.vue";
import Buttons from "./Buttons.vue"; import Buttons from "./Buttons.vue";
import ButtonSelector from "~/components/Common/ButtonSelector.vue";
import { promiseTimeout } from "@vueuse/core";
const store = useContactStore(); const store = useContactStore();
const achievements = useAchievementsStore();
const confirmationModal = useConfirmationModal();
const ACTIONS = {
github: [
"open",
"contact.actions.githubProfile",
"https://github.com/pihkaal",
],
email: ["copy", "contact.actions.email", "hello@pihkaal.me"],
website: ["copy", "contact.actions.websiteLink", "https://pihkaal.me"],
cv: ["open", "contact.actions.cv", "https://pihkaal.me/cv"],
} as const satisfies Record<
string,
[action: "copy" | "open", verbKey: string, content: string]
>;
const { selected, 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",
onActivate: (button) => {
actionateButton(button);
},
disabled: computed(() => store.isIntro || store.isOutro),
selectorAnimation: {
duration: 0.075,
ease: "none",
},
});
const handleActivateB = () => {
if (store.isIntro || store.isOutro) return;
store.animateOutro();
};
const actionateButton = async (button: (typeof selected)["value"]) => {
const [action, verbKey, content] = ACTIONS[button];
const verb = $t(verbKey);
if (action === "copy") {
try {
await navigator.clipboard.writeText(content);
store.pushNotification($t("contact.copiedToClipboard", { item: verb }));
} catch (error) {
console.error("Failed to copy to clipboard:", error);
}
} else {
const url = content.replace(/^https?:\/\//, "");
confirmationModal.open({
text: $t("contact.openUrl", { url }),
onActivateA: async () => {
store.pushNotification($t("contact.opened", { item: verb }));
await promiseTimeout(100);
await navigateTo(content, { open: { target: "_blank " } });
await promiseTimeout(500);
if (button === "github") {
achievements.unlock("contact_git_visit");
}
},
});
}
};
</script> </script>
<template> <template>
<Background /> <Background />
<Buttons /> <Buttons />
<ButtonSelector
:rect="selectorPosition"
:opacity="
store.isIntro ? store.intro.stage3Opacity : store.outro.stage1Opacity
"
/>
<CommonBars <CommonBars
:title="$t('contact.title')" :title="$t('contact.title')"
@@ -109,20 +17,4 @@ const actionateButton = async (button: (typeof selected)["value"]) => {
" "
:y-offset="store.isIntro ? store.intro.barOffsetY : 0" :y-offset="store.isIntro ? store.intro.barOffsetY : 0"
/> />
<CommonConfirmationModal />
<CommonButtons
:y-offset="
store.isIntro ? store.intro.barOffsetY : confirmationModal.buttonsYOffset
"
:opacity="
store.isIntro
? store.intro.stage3Opacity
: store.isOutro
? store.outro.stage2Opacity
: 1
"
:b-label="$t('common.quit')"
:a-label="$t(`contact.actions.${ACTIONS[selected][0]}`)"
@activate-b="handleActivateB"
/>
</template> </template>

View File

@@ -1,17 +1,162 @@
<script setup lang="ts"> <script setup lang="ts">
import ButtonSelector from "~/components/Common/ButtonSelector.vue";
import { promiseTimeout } from "@vueuse/core";
const { onRender } = useScreen(); const { onRender } = useScreen();
const store = useContactStore(); const store = useContactStore();
const { assets } = useAssets(); const achievements = useAchievementsStore();
const confirmationModal = useConfirmationModal();
const { assets } = useAssets((a) => a.images.contact.bottomScreen.buttons);
const BUTTONS = {
git: {
position: [31, 32],
action: "open",
url: "https://git.pihkaal.xyz",
text: "git.pihkaal.xyz",
},
email: {
position: [31, 64],
action: "copy",
url: "hello@pihkaal.me",
text: "hello@pihkaal.me",
},
linkedin: {
position: [31, 96],
action: "open",
url: "https://linkedin.com/in/stevancorre/",
text: "/in/stevancorre/",
},
cv: {
position: [31, 128],
action: "open",
url: "https://pihkaal.me/cv",
text: "/cv",
},
} as const satisfies Record<
string,
{
position: [number, number];
action: "copy" | "open";
url: string;
text: string;
}
>;
const { selected, pressed, selectorPosition } = useButtonNavigation({
buttons: {
git: [26, 27, 202, 42],
email: [26, 59, 202, 42],
linkedin: [26, 91, 202, 42],
cv: [26, 123, 202, 42],
},
navigation: {
git: {
down: "email",
},
email: {
up: "git",
down: "linkedin",
},
linkedin: {
up: "email",
down: "cv",
},
cv: {
up: "linkedin",
},
},
initialButton: "git",
onActivate: async (button) => {
const { action, url } = BUTTONS[button];
const verb = $t(`contact.actions.${button}`);
if (action === "copy") {
try {
await navigator.clipboard.writeText(url);
store.pushNotification($t("contact.copiedToClipboard", { item: verb }));
} catch (error) {
console.error("Failed to copy to clipboard:", error);
}
} else {
confirmationModal.open({
text: $t("contact.openUrl", { url: url.replace(/^https?:\/\//, "") }),
onActivateA: async () => {
store.pushNotification($t("contact.opened", { item: verb }));
await promiseTimeout(100);
await navigateTo(url, { open: { target: "_blank " } });
await promiseTimeout(500);
if (button === "git") {
achievements.unlock("contact_git_visit");
}
},
});
}
},
disabled: computed(() => store.isIntro || store.isOutro),
selectorAnimation: {
duration: 0.075,
ease: "none",
},
});
const handleActivateB = () => {
if (store.isIntro || store.isOutro) return;
store.animateOutro();
};
onRender((ctx) => { onRender((ctx) => {
ctx.globalAlpha = store.isIntro ctx.globalAlpha = store.isIntro
? store.intro.stage3Opacity ? store.intro.stage3Opacity
: store.outro.stage1Opacity; : store.outro.stage1Opacity;
assets.images.contact.bottomScreen.buttons.draw(ctx, 31, 32);
});
defineOptions({ ctx.textBaseline = "top";
render: () => null, ctx.font = "10px NDS10";
ctx.fillStyle = "#515151";
for (const [
buttonName,
{
text,
position: [x, y],
},
] of Object.entries(BUTTONS)) {
const button = buttonName as typeof selected.value;
const isPressed = pressed.value === button;
assets[isPressed ? "buttonPressed" : "button"].draw(ctx, x, y);
const pressedKey: `${typeof selected.value}Pressed` = `${button}Pressed`;
assets[isPressed ? pressedKey : button].draw(ctx, x + 1, y + 1);
ctx.fillText(text, x + 36, y + 11);
}
}); });
</script> </script>
<template>
<ButtonSelector
:rect="selectorPosition"
:opacity="
store.isIntro ? store.intro.stage3Opacity : store.outro.stage1Opacity
"
/>
<CommonConfirmationModal />
<CommonButtons
:y-offset="
store.isIntro ? store.intro.barOffsetY : confirmationModal.buttonsYOffset
"
:opacity="
store.isIntro
? store.intro.stage3Opacity
: store.isOutro
? store.outro.stage2Opacity
: 1
"
:b-label="$t('common.quit')"
:a-label="$t(`contact.actions.${BUTTONS[selected].action}`)"
@activate-b="handleActivateB"
/>
</template>

View File

@@ -5,9 +5,9 @@ import Selector from "~/components/Common/ButtonSelector.vue";
const { onRender } = useScreen(); const { onRender } = useScreen();
const store = useHomeStore(); const store = useHomeStore();
const { assets } = useAssets(); const { assets } = useAssets((a) => a.images.home.bottomScreen.buttons);
const { selected, selectorPosition } = useButtonNavigation({ const { selected, pressed, selectorPosition } = useButtonNavigation({
buttons: { buttons: {
projects: [31, 23, 193, 49], projects: [31, 23, 193, 49],
contact: [31, 71, 97, 49], contact: [31, 71, 97, 49],
@@ -20,12 +20,6 @@ const { selected, selectorPosition } = useButtonNavigation({
initialButton: store.selectedButton, initialButton: store.selectedButton,
onActivate: (button) => { onActivate: (button) => {
if (button === "theme") throw new Error(`Not implemented: ${button}`); if (button === "theme") throw new Error(`Not implemented: ${button}`);
if (button === "achievements") {
store.animateOutro("achievements");
return;
}
store.animateOutro(button); store.animateOutro(button);
}, },
navigation: { navigation: {
@@ -68,6 +62,14 @@ watch(selected, (newSelected) => {
store.selectedButton = newSelected; store.selectedButton = newSelected;
}); });
const getButtonImage = (button: typeof selected.value) => {
if (pressed.value === button) {
const pressedKey: `${typeof selected.value}Pressed` = `${button}Pressed`;
if (pressedKey in assets) return assets[pressedKey];
}
return assets[button];
};
const getButtonOffset = (button: (typeof selected)["value"]) => { const getButtonOffset = (button: (typeof selected)["value"]) => {
if (selected.value === button) return store.outro.buttonOffsetY; if (selected.value === button) return store.outro.buttonOffsetY;
return 0; return 0;
@@ -85,7 +87,7 @@ onRender((ctx) => {
// gallery // gallery
ctx.font = "7px NDS7"; ctx.font = "7px NDS7";
ctx.fillStyle = "#2c2c2c"; ctx.fillStyle = pressed.value === "gallery" ? "#2c2c2c" : "#282828";
fillTextCentered( fillTextCentered(
ctx, ctx,
$t("home.photoGallery"), $t("home.photoGallery"),
@@ -140,45 +142,40 @@ onRender((ctx) => {
:x="33" :x="33"
:y="25 + getButtonOffset('projects')" :y="25 + getButtonOffset('projects')"
:opacity="getOpacity('projects')" :opacity="getOpacity('projects')"
:image="assets.images.home.bottomScreen.buttons.game" :image="getButtonImage('projects')"
/> />
<Button <Button
:x="32" :x="32"
:y="72 + getButtonOffset('contact')" :y="72 + getButtonOffset('contact')"
:opacity="getOpacity('contact')" :opacity="getOpacity('contact')"
:image="assets.images.home.bottomScreen.buttons.contact" :image="getButtonImage('contact')"
/> />
<Button <Button
:x="128" :x="128"
:y="72 + getButtonOffset('gallery')" :y="72 + getButtonOffset('gallery')"
:opacity="getOpacity('gallery')" :opacity="getOpacity('gallery')"
:image="assets.images.home.bottomScreen.buttons.gallery" :image="getButtonImage('gallery')"
/> />
<Button <Button :x="33" :y="121" :opacity="getOpacity()" :image="assets.gamePak" />
:x="33"
:y="121"
:opacity="getOpacity()"
:image="assets.images.home.bottomScreen.buttons.gamePak"
/>
<Button <Button
:x="10" :x="10"
:y="175 + getButtonOffset('theme')" :y="175 + getButtonOffset('theme')"
:opacity="getOpacity('theme')" :opacity="getOpacity('theme')"
:image="assets.images.home.bottomScreen.buttons.theme" :image="getButtonImage('theme')"
/> />
<Button <Button
:x="117" :x="117"
:y="170 + getButtonOffset('settings')" :y="170 + getButtonOffset('settings')"
:opacity="getOpacity('settings')" :opacity="getOpacity('settings')"
:image="assets.images.home.bottomScreen.buttons.settings" :image="getButtonImage('settings')"
/> />
<Button <Button
:x="235" :x="235"
:y="175 + getButtonOffset('achievements')" :y="175 + getButtonOffset('achievements')"
:opacity="getOpacity('achievements')" :opacity="getOpacity('achievements')"
:image="assets.images.home.bottomScreen.buttons.achievements" :image="getButtonImage('achievements')"
/> />
<Selector :rect="selectorPosition" :opacity="getOpacity()" /> <Selector :rect="selectorPosition" :opacity="getOpacity()" />

View File

@@ -4,6 +4,9 @@ const props = withDefaults(
x: number; x: number;
y: number; y: number;
opacity?: number; opacity?: number;
pressed: string | null;
isAnyOtherMenuOpen: boolean;
submenuExtraOffsetY: number;
}>(), }>(),
{ {
opacity: 1, opacity: 1,
@@ -12,46 +15,29 @@ const props = withDefaults(
const { onRender } = useScreen(); const { onRender } = useScreen();
const settingsStore = useSettingsStore(); const store = useSettingsStore();
const menusContext = inject<{ const { assets } = useAssets((a) => a.images.settings.topScreen.clock);
isSubmenuSelected: ComputedRef<boolean>;
selectedSubmenuParent: ComputedRef<string | null>;
selectedSubmenuExtraOffsetY: ComputedRef<number>;
}>("menusContext")!;
const { assets } = useAssets();
const isOpen = computed( const isOpen = computed(
() => settingsStore.currentMenu === "clock" && settingsStore.menuExpanded, () => store.currentMenu === "clock" && store.menuExpanded,
); );
const isAnyOtherMenuOpen = computed(() => {
if (settingsStore.currentSubMenu) {
return !settingsStore.currentSubMenu.startsWith("clock");
}
if (menusContext.isSubmenuSelected.value) {
return menusContext.selectedSubmenuParent.value !== "clock";
}
return false;
});
const animation = useMenuAnimation("clock", isOpen); const animation = useMenuAnimation("clock", isOpen);
const submenuExtraOffset = (submenu: string) => const submenuExtraOffset = (submenu: string) =>
settingsStore.selectedButton === submenu store.selectedButton === submenu ? props.submenuExtraOffsetY : 0;
? menusContext.selectedSubmenuExtraOffsetY.value
: 0;
onRender((ctx) => { onRender((ctx) => {
ctx.globalAlpha = props.opacity; ctx.globalAlpha = props.opacity;
ctx.translate(props.x, props.y); ctx.translate(props.x, props.y);
if (isOpen.value || animation.playing) { if (isOpen.value || animation.playing) {
assets.images.settings.topScreen.clock.time.draw( (props.pressed === "clockTime" ? assets.timePressed : assets.time).draw(
ctx, ctx,
48 - animation.stage2Offset, 48 - animation.stage2Offset,
-48 + animation.stage1Offset + submenuExtraOffset("clockTime"), -48 + animation.stage1Offset + submenuExtraOffset("clockTime"),
); );
assets.images.settings.topScreen.clock.date.draw( (props.pressed === "clockDate" ? assets.datePressed : assets.date).draw(
ctx, ctx,
0, 0,
-96 + -96 +
@@ -59,17 +45,26 @@ onRender((ctx) => {
animation.stage1Offset + animation.stage1Offset +
submenuExtraOffset("clockDate"), submenuExtraOffset("clockDate"),
); );
assets.images.settings.topScreen.clock.achievements.draw( (props.pressed === "clockAchievements"
? assets.achievementsPressed
: assets.achievements
).draw(
ctx, ctx,
0, 0,
-48 + animation.stage1Offset + submenuExtraOffset("clockAchievements"), -48 + animation.stage1Offset + submenuExtraOffset("clockAchievements"),
); );
assets.images.settings.topScreen.clock.clockActive.draw(ctx, 0, 0); (props.pressed === "clock" ? assets.clockPressed : assets.clockActive).draw(
} else if (isAnyOtherMenuOpen.value) { ctx,
assets.images.settings.topScreen.clock.clockDisabled.draw(ctx, 0, 0); 0,
0,
);
} else if (props.pressed === "clock") {
assets.clockPressed.draw(ctx, 0, 0);
} else if (props.isAnyOtherMenuOpen) {
assets.clockDisabled.draw(ctx, 0, 0);
} else { } else {
assets.images.settings.topScreen.clock.clock.draw(ctx, 0, 0); assets.clock.draw(ctx, 0, 0);
} }
}); });

View File

@@ -19,20 +19,20 @@ import TouchScreenTapTap from "./TouchScreen/TapTap.vue";
import Selector from "~/components/Common/ButtonSelector.vue"; import Selector from "~/components/Common/ButtonSelector.vue";
const app = useAppStore(); const app = useAppStore();
const settingsStore = useSettingsStore(); const store = useSettingsStore();
const { assets } = useAssets(); const { assets } = useAssets();
const { onRender } = useScreen(); const { onRender } = useScreen();
onRender((ctx) => { onRender((ctx) => {
if (settingsStore.submenuBackground.opacity > 0) { if (store.submenuBackground.opacity > 0) {
ctx.globalAlpha = settingsStore.submenuBackground.opacity; ctx.globalAlpha = store.submenuBackground.opacity;
ctx.translate(0, settingsStore.submenuBackground.offsetY); ctx.translate(0, store.submenuBackground.offsetY);
assets.images.home.topScreen.background.draw(ctx, 0, 0); assets.images.home.topScreen.background.draw(ctx, 0, 0);
} }
}); });
if (app.previousScreen === "home") { if (app.previousScreen === "home") {
settingsStore.selectedButton = "options"; store.selectedButton = "options";
} }
const isMainMenu = (button: string): button is SettingsMenu => const isMainMenu = (button: string): button is SettingsMenu =>
@@ -47,7 +47,7 @@ const getParentMenu = (submenu: string): SettingsMenu => {
return match[1] as SettingsMenu; return match[1] as SettingsMenu;
}; };
const { select, selected, selectorPosition } = useButtonNavigation({ const { select, selected, pressed, selectorPosition } = useButtonNavigation({
buttons: { buttons: {
options: [31, 119, 49, 49], options: [31, 119, 49, 49],
optionsLanguage: [31, 71, 49, 49], optionsLanguage: [31, 71, 49, 49],
@@ -67,16 +67,15 @@ const { select, selected, selectorPosition } = useButtonNavigation({
touchScreen: [175, 119, 49, 49], touchScreen: [175, 119, 49, 49],
}, },
initialButton: settingsStore.selectedButton, initialButton: store.selectedButton,
onActivate: (buttonName) => { onActivate: (buttonName) => {
if (isSubMenu(buttonName)) { if (isSubMenu(buttonName)) {
settingsStore.openSubMenu(buttonName); store.openSubMenu(buttonName);
} else { } else {
if (buttonName === "options") select("optionsLanguage"); if (buttonName === "options") select("optionsLanguage");
if (buttonName === "clock") select("clockAchievements"); if (buttonName === "clock") select("clockAchievements");
if (buttonName === "user") select("userUserName"); if (buttonName === "user") select("userUserName");
if (buttonName === "touchScreen") if (buttonName === "touchScreen") store.openSubMenu("touchScreenTapTap");
settingsStore.openSubMenu("touchScreenTapTap");
} }
}, },
navigation: { navigation: {
@@ -153,17 +152,17 @@ const { select, selected, selectorPosition } = useButtonNavigation({
canClickButton: (buttonName) => { canClickButton: (buttonName) => {
if (isSubMenu(buttonName)) { if (isSubMenu(buttonName)) {
const parent = getParentMenu(buttonName); const parent = getParentMenu(buttonName);
return settingsStore.currentMenu === parent && settingsStore.menuExpanded; return store.currentMenu === parent && store.menuExpanded;
} }
return true; return true;
}, },
disabled: computed( disabled: computed(
() => () =>
settingsStore.currentSubMenu !== null || store.currentSubMenu !== null ||
settingsStore.submenuTransition.opacity < 1 || store.submenuTransition.opacity < 1 ||
settingsStore.isIntro || store.isIntro ||
settingsStore.isOutro || store.isOutro ||
settingsStore.animatingNotification, store.animatingNotification,
), ),
selectorAnimation: { selectorAnimation: {
duration: 0.11, duration: 0.11,
@@ -172,34 +171,34 @@ const { select, selected, selectorPosition } = useButtonNavigation({
}); });
const isSubmenuSelected = computed(() => isSubMenu(selected.value)); const isSubmenuSelected = computed(() => isSubMenu(selected.value));
const selectedSubmenuParent = computed(() =>
isSubmenuSelected.value ? getParentMenu(selected.value) : null,
);
const selectedSubmenuExtraOffsetY = computed( const selectedSubmenuExtraOffsetY = computed(
() => () =>
settingsStore.submenuTransition.selectorOffsetY - store.submenuTransition.selectorOffsetY - store.submenuTransition.offsetY,
settingsStore.submenuTransition.offsetY,
); );
provide("menusContext", { const isAnyOtherMenuOpen = (menu: SettingsMenu) => {
isSubmenuSelected, if (store.currentSubMenu) {
selectedSubmenuParent, return !store.currentSubMenu.startsWith(menu);
selectedSubmenuExtraOffsetY, }
}); if (isSubmenuSelected.value) {
return getParentMenu(selected.value) !== menu;
}
return false;
};
watch( watch(
selected, selected,
(newSelected) => { (newSelected) => {
settingsStore.selectedButton = newSelected; store.selectedButton = newSelected;
if (settingsStore.currentSubMenu === null) { if (store.currentSubMenu === null) {
if (isMainMenu(newSelected)) { if (isMainMenu(newSelected)) {
settingsStore.openMenu(newSelected, false); store.openMenu(newSelected, false);
} else if (isSubMenu(newSelected)) { } else if (isSubMenu(newSelected)) {
const parentMenu = getParentMenu(newSelected); const parentMenu = getParentMenu(newSelected);
if (parentMenu) { if (parentMenu) {
settingsStore.openMenu(parentMenu, true); store.openMenu(parentMenu, true);
} }
} }
} }
@@ -231,18 +230,13 @@ const selectorXOffset = computed(() => {
switch (menu) { switch (menu) {
case "clock": case "clock":
return Math.min(0, settingsStore.menuOffsets[1]); return Math.min(0, store.menuOffsets[1]);
case "user": case "user":
return Math.min( return Math.min(0, store.menuOffsets[1] + store.menuOffsets[2]);
0,
settingsStore.menuOffsets[1] + settingsStore.menuOffsets[2],
);
case "touchScreen": case "touchScreen":
return Math.min( return Math.min(
0, 0,
settingsStore.menuOffsets[1] + store.menuOffsets[1] + store.menuOffsets[2] + store.menuOffsets[3],
settingsStore.menuOffsets[2] +
settingsStore.menuOffsets[3],
); );
default: default:
return 0; return 0;
@@ -251,91 +245,81 @@ const selectorXOffset = computed(() => {
const selectorTransitionOffsetY = computed(() => { const selectorTransitionOffsetY = computed(() => {
if (isSubmenuSelected.value || selected.value === "touchScreen") { if (isSubmenuSelected.value || selected.value === "touchScreen") {
return settingsStore.submenuTransition.selectorOffsetY; return store.submenuTransition.selectorOffsetY;
} }
return settingsStore.submenuTransition.offsetY; return store.submenuTransition.offsetY;
}); });
const handleActivateB = () => { const handleActivateB = () => {
if ( if (store.isIntro || store.isOutro || store.submenuTransition.opacity < 1)
settingsStore.isIntro ||
settingsStore.isOutro ||
settingsStore.submenuTransition.opacity < 1
)
return; return;
if (isSubmenuSelected.value) { if (isSubmenuSelected.value) {
select(getParentMenu(selected.value)); select(getParentMenu(selected.value));
} else { } else {
settingsStore.animateOutro(); store.animateOutro();
} }
}; };
</script> </script>
<template> <template>
<template v-if="!settingsStore.currentSubMenu"> <template v-if="!store.currentSubMenu">
<TouchScreenMenu <TouchScreenMenu
:x=" :x="
177 + 177 + store.menuOffsets[1] + store.menuOffsets[2] + store.menuOffsets[3]
settingsStore.menuOffsets[1] +
settingsStore.menuOffsets[2] +
settingsStore.menuOffsets[3]
" "
:y=" :y="
121 + 121 +
settingsStore.menuYOffset + store.menuYOffset +
(selected === 'touchScreen' (selected === 'touchScreen'
? settingsStore.submenuTransition.selectorOffsetY ? store.submenuTransition.selectorOffsetY
: settingsStore.submenuTransition.offsetY) : store.submenuTransition.offsetY)
" "
:opacity="settingsStore.submenuTransition.opacity" :opacity="store.submenuTransition.opacity"
:pressed="pressed"
:is-any-other-menu-open="isAnyOtherMenuOpen('touchScreen')"
/> />
<UserMenu <UserMenu
:x="129 + settingsStore.menuOffsets[1] + settingsStore.menuOffsets[2]" :x="129 + store.menuOffsets[1] + store.menuOffsets[2]"
:y=" :y="121 + store.menuYOffset + store.submenuTransition.offsetY"
121 + :opacity="store.submenuTransition.opacity"
settingsStore.menuYOffset + :pressed="pressed"
settingsStore.submenuTransition.offsetY :is-any-other-menu-open="isAnyOtherMenuOpen('user')"
" :submenu-extra-offset-y="selectedSubmenuExtraOffsetY"
:opacity="settingsStore.submenuTransition.opacity"
/> />
<ClockMenu <ClockMenu
:x="81 + settingsStore.menuOffsets[1]" :x="81 + store.menuOffsets[1]"
:y=" :y="121 + store.menuYOffset + store.submenuTransition.offsetY"
121 + :opacity="store.submenuTransition.opacity"
settingsStore.menuYOffset + :pressed="pressed"
settingsStore.submenuTransition.offsetY :is-any-other-menu-open="isAnyOtherMenuOpen('clock')"
" :submenu-extra-offset-y="selectedSubmenuExtraOffsetY"
:opacity="settingsStore.submenuTransition.opacity"
/> />
<OptionsMenu <OptionsMenu
:x="33" :x="33"
:y=" :y="121 + store.menuYOffset + store.submenuTransition.offsetY"
121 + :opacity="store.submenuTransition.opacity"
settingsStore.menuYOffset + :pressed="pressed"
settingsStore.submenuTransition.offsetY :is-any-other-menu-open="isAnyOtherMenuOpen('options')"
" :submenu-extra-offset-y="selectedSubmenuExtraOffsetY"
:opacity="settingsStore.submenuTransition.opacity"
/> />
<Selector <Selector
:rect="[ :rect="[
selectorPosition[0] + selectorXOffset, selectorPosition[0] + selectorXOffset,
selectorPosition[1] + selectorPosition[1] + store.menuYOffset + selectorTransitionOffsetY,
settingsStore.menuYOffset +
selectorTransitionOffsetY,
selectorPosition[2], selectorPosition[2],
selectorPosition[3], selectorPosition[3],
]" ]"
:opacity="settingsStore.submenuTransition.opacity" :opacity="store.submenuTransition.opacity"
/> />
<CommonButtons <CommonButtons
:y-offset="settingsStore.barOffsetY + settingsStore.submenuButtonsOffsetY" :y-offset="store.barOffsetY + store.submenuButtonsOffsetY"
:b-label="isSubmenuSelected ? $t('common.goBack') : $t('common.quit')" :b-label="isSubmenuSelected ? $t('common.goBack') : $t('common.quit')"
:a-label="$t('common.select')" :a-label="$t('common.select')"
@activate-b="handleActivateB()" @activate-b="handleActivateB()"
/> />
</template> </template>
<component :is="viewComponents[settingsStore.currentSubMenu]" v-else /> <component :is="viewComponents[store.currentSubMenu]" v-else />
</template> </template>

View File

@@ -246,13 +246,21 @@ onRender((ctx) => {
onRender((ctx) => { onRender((ctx) => {
ctx.translate(0, intro.scoreOffsetY); ctx.translate(0, intro.scoreOffsetY);
drawButton(ctx, `${$t("settings.options.2048.score")}: ${score}`, 10, 2, 118); drawButton(
ctx,
`${$t("settings.options.2048.score")}: ${score}`,
10,
2,
118,
false,
);
drawButton( drawButton(
ctx, ctx,
`${$t("settings.options.2048.highScore")}: ${savedState.value.highScore}`, `${$t("settings.options.2048.highScore")}: ${savedState.value.highScore}`,
138, 138,
2, 2,
108, 108,
false,
); );
}, 110); }, 110);

View File

@@ -4,6 +4,9 @@ const props = withDefaults(
x: number; x: number;
y: number; y: number;
opacity?: number; opacity?: number;
pressed: string | null;
isAnyOtherMenuOpen: boolean;
submenuExtraOffsetY: number;
}>(), }>(),
{ {
opacity: 1, opacity: 1,
@@ -12,46 +15,32 @@ const props = withDefaults(
const { onRender } = useScreen(); const { onRender } = useScreen();
const settingsStore = useSettingsStore(); const store = useSettingsStore();
const menusContext = inject<{ const { assets } = useAssets((a) => a.images.settings.topScreen.options);
isSubmenuSelected: ComputedRef<boolean>;
selectedSubmenuParent: ComputedRef<string | null>;
selectedSubmenuExtraOffsetY: ComputedRef<number>;
}>("menusContext")!;
const { assets } = useAssets();
const isOpen = computed( const isOpen = computed(
() => settingsStore.currentMenu === "options" && settingsStore.menuExpanded, () => store.currentMenu === "options" && store.menuExpanded,
); );
const isAnyOtherMenuOpen = computed(() => {
if (settingsStore.currentSubMenu) {
return !settingsStore.currentSubMenu.startsWith("options");
}
if (menusContext.isSubmenuSelected.value) {
return menusContext.selectedSubmenuParent.value !== "options";
}
return false;
});
const animation = useMenuAnimation("options", isOpen); const animation = useMenuAnimation("options", isOpen);
const submenuExtraOffset = (submenu: string) => const submenuExtraOffset = (submenu: string) =>
settingsStore.selectedButton === submenu store.selectedButton === submenu ? props.submenuExtraOffsetY : 0;
? menusContext.selectedSubmenuExtraOffsetY.value
: 0;
onRender((ctx) => { onRender((ctx) => {
ctx.globalAlpha = props.opacity; ctx.globalAlpha = props.opacity;
ctx.translate(props.x, props.y); ctx.translate(props.x, props.y);
if (isOpen.value || animation.playing) { if (isOpen.value || animation.playing) {
assets.images.settings.topScreen.options._2048.draw( (props.pressed === "options2048" ? assets._2048Pressed : assets._2048).draw(
ctx, ctx,
48 - animation.stage2Offset, 48 - animation.stage2Offset,
-48 + animation.stage1Offset + submenuExtraOffset("options2048"), -48 + animation.stage1Offset + submenuExtraOffset("options2048"),
); );
assets.images.settings.topScreen.options.renderingMode.draw( (props.pressed === "optionsRenderingMode"
? assets.renderingModePressed
: assets.renderingMode
).draw(
ctx, ctx,
0, 0,
-96 + -96 +
@@ -59,17 +48,25 @@ onRender((ctx) => {
animation.stage1Offset + animation.stage1Offset +
submenuExtraOffset("optionsRenderingMode"), submenuExtraOffset("optionsRenderingMode"),
); );
assets.images.settings.topScreen.options.language.draw( (props.pressed === "optionsLanguage"
? assets.languagePressed
: assets.language
).draw(
ctx, ctx,
0, 0,
-48 + animation.stage1Offset + submenuExtraOffset("optionsLanguage"), -48 + animation.stage1Offset + submenuExtraOffset("optionsLanguage"),
); );
assets.images.settings.topScreen.options.optionsActive.draw(ctx, 0, 0); (props.pressed === "options"
} else if (isAnyOtherMenuOpen.value) { ? assets.optionsPressed
assets.images.settings.topScreen.options.optionsDisabled.draw(ctx, 0, 0); : assets.optionsActive
).draw(ctx, 0, 0);
} else if (props.pressed === "options") {
assets.optionsPressed.draw(ctx, 0, 0);
} else if (props.isAnyOtherMenuOpen) {
assets.optionsDisabled.draw(ctx, 0, 0);
} else { } else {
assets.images.settings.topScreen.options.options.draw(ctx, 0, 0); assets.options.draw(ctx, 0, 0);
} }
}); });

View File

@@ -4,6 +4,8 @@ const props = withDefaults(
x: number; x: number;
y: number; y: number;
opacity?: number; opacity?: number;
pressed: string | null;
isAnyOtherMenuOpen: boolean;
}>(), }>(),
{ {
opacity: 1, opacity: 1,
@@ -12,39 +14,16 @@ const props = withDefaults(
const { onRender } = useScreen(); const { onRender } = useScreen();
const settingsStore = useSettingsStore(); const { assets } = useAssets((a) => a.images.settings.topScreen.touchScreen);
const menusContext = inject<{
isSubmenuSelected: ComputedRef<boolean>;
selectedSubmenuParent: ComputedRef<string | null>;
}>("menusContext")!;
const { assets } = useAssets();
// TODO: i don't like this
const isAnyOtherMenuOpen = computed(() => {
if (settingsStore.currentSubMenu) {
return !settingsStore.currentSubMenu.startsWith("touchScreen");
}
if (menusContext.isSubmenuSelected.value) {
return menusContext.selectedSubmenuParent.value !== "touchScreen";
}
return false;
});
onRender((ctx) => { onRender((ctx) => {
ctx.globalAlpha = props.opacity; ctx.globalAlpha = props.opacity;
if (isAnyOtherMenuOpen.value) { if (props.pressed === "touchScreen") {
assets.images.settings.topScreen.touchScreen.touchScreenDisabled.draw( assets.touchScreenPressed.draw(ctx, props.x, props.y);
ctx, } else if (props.isAnyOtherMenuOpen) {
props.x, assets.touchScreenDisabled.draw(ctx, props.x, props.y);
props.y,
);
} else { } else {
assets.images.settings.topScreen.touchScreen.touchScreen.draw( assets.touchScreen.draw(ctx, props.x, props.y);
ctx,
props.x,
props.y,
);
} }
}); });

View File

@@ -393,6 +393,7 @@ onRender((ctx) => {
10, 10,
2, 2,
88, 88,
false,
); );
drawButton( drawButton(
ctx, ctx,
@@ -400,6 +401,7 @@ onRender((ctx) => {
108, 108,
2, 2,
88, 88,
false,
); );
drawButton( drawButton(
ctx, ctx,
@@ -407,6 +409,7 @@ onRender((ctx) => {
206, 206,
2, 2,
40, 40,
false,
); );
}, 110); }, 110);

View File

@@ -4,6 +4,9 @@ const props = withDefaults(
x: number; x: number;
y: number; y: number;
opacity?: number; opacity?: number;
pressed: string | null;
isAnyOtherMenuOpen: boolean;
submenuExtraOffsetY: number;
}>(), }>(),
{ {
opacity: 1, opacity: 1,
@@ -12,51 +15,37 @@ const props = withDefaults(
const { onRender } = useScreen(); const { onRender } = useScreen();
const settingsStore = useSettingsStore(); const store = useSettingsStore();
const menusContext = inject<{ const { assets } = useAssets((a) => a.images.settings.topScreen.user);
isSubmenuSelected: ComputedRef<boolean>;
selectedSubmenuParent: ComputedRef<string | null>;
selectedSubmenuExtraOffsetY: ComputedRef<number>;
}>("menusContext")!;
const { assets } = useAssets();
const isOpen = computed( const isOpen = computed(
() => settingsStore.currentMenu === "user" && settingsStore.menuExpanded, () => store.currentMenu === "user" && store.menuExpanded,
); );
const isAnyOtherMenuOpen = computed(() => {
if (settingsStore.currentSubMenu) {
return !settingsStore.currentSubMenu.startsWith("user");
}
if (menusContext.isSubmenuSelected.value) {
return menusContext.selectedSubmenuParent.value !== "user";
}
return false;
});
const animation = useMenuAnimation("user", isOpen); const animation = useMenuAnimation("user", isOpen);
const submenuExtraOffset = (submenu: string) => const submenuExtraOffset = (submenu: string) =>
settingsStore.selectedButton === submenu store.selectedButton === submenu ? props.submenuExtraOffsetY : 0;
? menusContext.selectedSubmenuExtraOffsetY.value
: 0;
onRender((ctx) => { onRender((ctx) => {
ctx.globalAlpha = props.opacity; ctx.globalAlpha = props.opacity;
ctx.translate(props.x, props.y); ctx.translate(props.x, props.y);
if (isOpen.value || animation.playing) { if (isOpen.value || animation.playing) {
assets.images.settings.topScreen.user.birthday.draw( (props.pressed === "userBirthday"
? assets.birthdayPressed
: assets.birthday
).draw(
ctx, ctx,
-48 + animation.stage2Offset, -48 + animation.stage2Offset,
-48 + animation.stage1Offset + submenuExtraOffset("userBirthday"), -48 + animation.stage1Offset + submenuExtraOffset("userBirthday"),
); );
assets.images.settings.topScreen.user.snake.draw( (props.pressed === "userSnake" ? assets.snakePressed : assets.snake).draw(
ctx, ctx,
48 - animation.stage2Offset, 48 - animation.stage2Offset,
-48 + animation.stage1Offset + submenuExtraOffset("userSnake"), -48 + animation.stage1Offset + submenuExtraOffset("userSnake"),
); );
assets.images.settings.topScreen.user.color.draw( (props.pressed === "userColor" ? assets.colorPressed : assets.color).draw(
ctx, ctx,
0, 0,
-96 + -96 +
@@ -64,17 +53,26 @@ onRender((ctx) => {
animation.stage1Offset + animation.stage1Offset +
submenuExtraOffset("userColor"), submenuExtraOffset("userColor"),
); );
assets.images.settings.topScreen.user.userName.draw( (props.pressed === "userUserName"
? assets.userNamePressed
: assets.userName
).draw(
ctx, ctx,
0, 0,
-48 + animation.stage1Offset + submenuExtraOffset("userUserName"), -48 + animation.stage1Offset + submenuExtraOffset("userUserName"),
); );
assets.images.settings.topScreen.user.userActive.draw(ctx, 0, 0); (props.pressed === "user" ? assets.userPressed : assets.userActive).draw(
} else if (isAnyOtherMenuOpen.value) { ctx,
assets.images.settings.topScreen.user.userDisabled.draw(ctx, 0, 0); 0,
0,
);
} else if (props.pressed === "user") {
assets.userPressed.draw(ctx, 0, 0);
} else if (props.isAnyOtherMenuOpen) {
assets.userDisabled.draw(ctx, 0, 0);
} else { } else {
assets.images.settings.topScreen.user.user.draw(ctx, 0, 0); assets.user.draw(ctx, 0, 0);
} }
}); });

View File

@@ -292,13 +292,14 @@ onRender((ctx) => {
onRender((ctx) => { onRender((ctx) => {
ctx.translate(0, intro.scoreOffsetY); ctx.translate(0, intro.scoreOffsetY);
drawButton(ctx, $t("settings.user.snake.score", { score }), 10, 2, 118); drawButton(ctx, $t("settings.user.snake.score", { score }), 10, 2, 118, true);
drawButton( drawButton(
ctx, ctx,
$t("settings.user.snake.best", { best: highScore.value }), $t("settings.user.snake.best", { best: highScore.value }),
138, 138,
2, 2,
108, 108,
true,
); );
}, 110); }, 110);

View File

@@ -210,16 +210,8 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
} }
}); });
const handleMouseUp = () => { useMouseUp(() => {
pressedButton.value = null; pressedButton.value = null;
};
onMounted(() => {
document.addEventListener("mouseup", handleMouseUp);
});
onUnmounted(() => {
document.removeEventListener("mouseup", handleMouseUp);
}); });
onClick((x: number, y: number) => { onClick((x: number, y: number) => {

View File

@@ -162,6 +162,7 @@ export const drawButton = (
x: number, x: number,
y: number, y: number,
width: number, width: number,
pressed: boolean,
) => { ) => {
const { assets } = useAssets((a) => a.images.common); const { assets } = useAssets((a) => a.images.common);
@@ -175,13 +176,21 @@ export const drawButton = (
ctx.save(); ctx.save();
ctx.translate(x, y); ctx.translate(x, y);
assets.buttonLeft.draw(ctx, 0, 0); (pressed ? assets.buttonLeftPressed : assets.buttonLeft).draw(ctx, 0, 0);
for (let i = 0; i < bodyCount; i++) { for (let i = 0; i < bodyCount; i++) {
assets.buttonBody.draw(ctx, LEFT_WIDTH + i * BODY_WIDTH, 0); (pressed ? assets.buttonBodyPressed : assets.buttonBody).draw(
ctx,
LEFT_WIDTH + i * BODY_WIDTH,
0,
);
} }
assets.buttonRight.draw(ctx, width - RIGHT_WIDTH, 0); (pressed ? assets.buttonRightPressed : assets.buttonRight).draw(
ctx,
width - RIGHT_WIDTH,
0,
);
ctx.textBaseline = "top"; ctx.textBaseline = "top";
ctx.font = "10px NDS10"; ctx.font = "10px NDS10";

View File

@@ -15,3 +15,13 @@ export const mapKeyToNDS = (key: string): string | null => {
key = key.length === 1 ? key.toUpperCase() : key; key = key.length === 1 ? key.toUpperCase() : key;
return KEY_TO_NDS_BUTTON[key] ?? null; return KEY_TO_NDS_BUTTON[key] ?? null;
}; };
export const useMouseUp = (callback: () => void) => {
onMounted(() => {
document.addEventListener("mouseup", callback);
});
onUnmounted(() => {
document.removeEventListener("mouseup", callback);
});
};

View File

@@ -161,9 +161,9 @@
"actions": { "actions": {
"open": "Open", "open": "Open",
"copy": "Copy", "copy": "Copy",
"githubProfile": "Github profile", "git": "Github profile",
"email": "Email", "email": "Email",
"websiteLink": "Website link", "linkedin": "LinkedIn link",
"cv": "CV" "cv": "CV"
}, },
"copiedToClipboard": "{item} copied to clipboard", "copiedToClipboard": "{item} copied to clipboard",

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 B

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 B

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 B

After

Width:  |  Height:  |  Size: 92 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

View File

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B