feat(settings/options/language): intro and outro animation

This commit is contained in:
2026-02-06 13:41:12 +01:00
parent 7791f52ddf
commit 0f70c7fef0

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import gsap from "gsap";
const { locales, locale, setLocale } = useI18n();
const store = useSettingsStore();
const confirmationModal = useConfirmationModal();
@@ -70,7 +72,65 @@ const { selected, selectorPosition } = useButtonNavigation({
},
});
const handleCancel = () => {
const SLIDE_OFFSET = 48;
const SLIDE_DURATION = 0.15;
const OUTRO_OFFSET = 96;
const OUTRO_DURATION = 0.25;
const ROW_STAGGER = SLIDE_DURATION + 0.075;
const ROW_COUNT = 3;
const selectedRow = computed(() => {
const index = BUTTON_KEYS.indexOf(selected.value);
return Math.floor(index / 2);
});
const animation = reactive({
rowOffsetY: Array(ROW_COUNT).fill(SLIDE_OFFSET) as number[],
rowOpacity: Array(ROW_COUNT).fill(0) as number[],
});
const animateIntro = async () => {
const timeline = gsap.timeline();
for (let i = 0; i < ROW_COUNT; i++) {
timeline
.to(
animation.rowOffsetY,
{ [i]: 0, duration: SLIDE_DURATION, ease: "none" },
i * ROW_STAGGER,
)
.to(
animation.rowOpacity,
{ [i]: 1, duration: SLIDE_DURATION, ease: "none" },
i * ROW_STAGGER,
);
}
await timeline;
};
const animateOutro = async () => {
const timeline = gsap.timeline();
for (let i = 0; i < ROW_COUNT; i++) {
timeline
.to(
animation.rowOffsetY,
{ [i]: OUTRO_OFFSET, duration: OUTRO_DURATION, ease: "none" },
0,
)
.to(
animation.rowOpacity,
{ [i]: 0, duration: OUTRO_DURATION, ease: "none" },
0,
);
}
await timeline;
};
onMounted(() => {
animateIntro();
});
const handleCancel = async () => {
await animateOutro();
store.closeSubMenu();
};
@@ -92,7 +152,10 @@ const handleConfirm = () => {
{},
{ locale: selectedLocale.code },
),
onClosed: () => store.closeSubMenu(),
onClosed: async () => {
await animateOutro();
store.closeSubMenu();
},
timeout: 2000,
});
};
@@ -102,18 +165,25 @@ onRender((ctx) => {
ctx.fillStyle = "#010101";
for (let i = 0; i < locales.value.length; i += 1) {
const row = Math.floor(i / 2);
const [x, y] = BUTTON_POSITIONS[i]!;
const isSelected = selected.value === BUTTON_KEYS[i];
const buttonImage = isSelected
? assets.images.settings.bottomScreen.options.languageButtonActive
: assets.images.settings.bottomScreen.options.languageButton;
ctx.save();
ctx.globalAlpha = animation.rowOpacity[row]!;
ctx.translate(0, animation.rowOffsetY[row]!);
buttonImage.draw(ctx, x, y, isSelected ? { colored: true } : undefined);
const name = locales.value[i]?.name;
if (!name) {
throw new Error(`Missing locale or locale name: ${BUTTON_KEYS[i]}`);
}
fillTextHCentered(ctx, name, x, y + 20, 96);
ctx.restore();
}
});
@@ -131,5 +201,13 @@ defineOptions({
@activate-a="handleConfirm"
/>
<CommonButtonSelector :rect="selectorPosition" />
<CommonButtonSelector
:rect="[
selectorPosition[0],
selectorPosition[1] + animation.rowOffsetY[selectedRow]!,
selectorPosition[2],
selectorPosition[3],
]"
:opacity="animation.rowOpacity[selectedRow]!"
/>
</template>