Compare commits

...

6 Commits

15 changed files with 85 additions and 64 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -6,6 +6,8 @@ const { onRender } = useScreen();
const achievements = useAchievementsStore(); const achievements = useAchievementsStore();
const { assets } = useAssets(); const { assets } = useAssets();
const confetti = useConfetti();
const queue = ref<Achievement[]>([]); const queue = ref<Achievement[]>([]);
const currentAchievement = ref<Achievement | null>(null); const currentAchievement = ref<Achievement | null>(null);
const x = ref(LOGICAL_WIDTH); const x = ref(LOGICAL_WIDTH);
@@ -20,16 +22,16 @@ const NOTIF_X_VISIBLE = LOGICAL_WIDTH - NOTIF_WIDTH;
const TEXT_X_OFFSET = LOGO_SIZE + PADDING * 2; const TEXT_X_OFFSET = LOGO_SIZE + PADDING * 2;
const LINE_HEIGHT = 8; const LINE_HEIGHT = 8;
achievements.$onAction(({ name, args, after }) => { watch(
if (name === "unlock") { () => achievements.unlocked,
after((wasUnlocked) => { (newVal, oldVal) => {
if (wasUnlocked) { const added = newVal.filter((id) => !oldVal?.includes(id));
queue.value.push(args[0]); for (const id of added) {
processQueue(); queue.value.push(id);
} }
}); if (added.length > 0) processQueue();
} },
}); );
const processQueue = () => { const processQueue = () => {
if (isAnimating.value || queue.value.length === 0) return; if (isAnimating.value || queue.value.length === 0) return;
@@ -40,6 +42,14 @@ const processQueue = () => {
currentAchievement.value = next; currentAchievement.value = next;
isAnimating.value = true; isAnimating.value = true;
assets.audio.messageReceived.play(0.5);
if (next === "all_achievements") {
confetti.spawn();
} else {
confetti.spawn(50, 175);
}
gsap gsap
.timeline() .timeline()
.to(x, { .to(x, {

View File

@@ -100,6 +100,8 @@ const handleActivateA = async (button: typeof selected.value) => {
if (button === "git") { if (button === "git") {
achievements.unlock("contact_git_visit"); achievements.unlock("contact_git_visit");
} else if (button === "linkedin") {
achievements.unlock("contact_linkedin_visit");
} }
}, },
}); });

View File

@@ -199,7 +199,7 @@ watch(
const { onRender, onBeforeRender } = useLoop(); const { onRender, onBeforeRender } = useLoop();
const LAG_FPS_THRESHOLD = 40; const LAG_FPS_THRESHOLD = 30;
const LAG_DURATION_SECS = 5; const LAG_DURATION_SECS = 5;
let lagSeconds = 0; let lagSeconds = 0;
let lagCheckDone = false; let lagCheckDone = false;
@@ -352,7 +352,7 @@ onBeforeRender(() => {
const delta = (now - lastFrameTime) / 1000; const delta = (now - lastFrameTime) / 1000;
lastFrameTime = now; lastFrameTime = now;
if (document.hidden || delta > 0.5) { if (document.hidden || delta > 0.5 || !app.booted) {
lagSeconds = 0; lagSeconds = 0;
return; return;
} }

View File

@@ -27,16 +27,17 @@ const achievementAssets =
assets.images.settings.bottomScreen.clock.achievements; assets.images.settings.bottomScreen.clock.achievements;
const { onRender, onClick } = useScreen(); const { onRender, onClick } = useScreen();
const app = useAppStore();
const store = useSettingsStore(); const store = useSettingsStore();
const app = useAppStore();
const achievements = useAchievementsStore(); const achievements = useAchievementsStore();
const achievementsScreen = useAchievementsScreen(); const achievementsScreen = useAchievementsScreen();
const confirmationModal = useConfirmationModal(); const confirmationModal = useConfirmationModal();
const isAnimating = ref(true); const comingFromAchievements = store.returningFromAchievements;
const bLabel = ref($t("common.goBack")); const isAnimating = ref(!comingFromAchievements);
const aLabel = ref($t("common.select")); const bLabel = ref(comingFromAchievements ? $t("common.cancel") : $t("common.goBack"));
const aLabel = ref(comingFromAchievements ? $t("common.reset") : $t("common.select"));
const SLIDE_OFFSET = 96; const SLIDE_OFFSET = 96;
const SLIDE_DURATION = 0.25; const SLIDE_DURATION = 0.25;
@@ -45,9 +46,9 @@ const ARROW_SLIDE_DURATION = 0.167;
const VIEW_ALL_OFFSET = -20; const VIEW_ALL_OFFSET = -20;
const animation = reactive({ const animation = reactive({
offsetY: SLIDE_OFFSET, offsetY: comingFromAchievements ? 0 : SLIDE_OFFSET,
opacity: 0, opacity: comingFromAchievements ? 1 : 0,
viewAllOffsetY: VIEW_ALL_OFFSET, viewAllOffsetY: comingFromAchievements ? 0 : VIEW_ALL_OFFSET,
}); });
const obtainedRef = const obtainedRef =
@@ -106,6 +107,12 @@ const animateOutro = async () => {
}; };
onMounted(() => { onMounted(() => {
store.returningFromAchievements = false;
if (comingFromAchievements) {
obtainedRef.value?.showInstant();
totalRef.value?.showInstant();
return;
}
animateIntro(); animateIntro();
}); });
@@ -132,6 +139,7 @@ const handleReset = () => {
const handleVisitAll = () => { const handleVisitAll = () => {
if (isAnimating.value) return; if (isAnimating.value) return;
assets.audio.menuOpen.play(); assets.audio.menuOpen.play();
store.returningFromAchievements = true;
achievementsScreen.animateFadeToBlackIntro(); achievementsScreen.animateFadeToBlackIntro();
}; };
@@ -164,7 +172,7 @@ onRender((ctx) => {
? APP_COLOR_TO_FONT_COLOR[app.color.hex]! ? APP_COLOR_TO_FONT_COLOR[app.color.hex]!
: "#fbfbfb"; : "#fbfbfb";
ctx.fillText("/", 7 * 16 + 3, 4 * 16 + 4 + 39); ctx.fillText("/", 7 * 16 + 3, 4 * 16 + 4 + 39);
}); }, 5);
onRender((ctx) => { onRender((ctx) => {
ctx.translate(0, animation.viewAllOffsetY); ctx.translate(0, animation.viewAllOffsetY);

View File

@@ -35,7 +35,9 @@ const animation = reactive({
isIntro: true, isIntro: true,
isOutro: false, isOutro: false,
}); });
const isAnimating = computed(() => animation.isIntro || animation.isOutro); const isAnimating = computed(
() => animation.isIntro || animation.isOutro || confirmationModal.isOpen,
);
const animateIntro = (): gsap.core.Timeline => { const animateIntro = (): gsap.core.Timeline => {
animation.isIntro = true; animation.isIntro = true;
@@ -289,13 +291,6 @@ const handleActivateA = () => {
achievements.unlock("settings_color_change"); achievements.unlock("settings_color_change");
} }
if (!achievements.advancement.colors.includes(app.color.hex)) {
achievements.advancement.colors.push(app.color.hex);
if (achievements.advancement.colors.length === APP_COLORS.flat().length) {
achievements.unlock("settings_color_try_all");
}
}
confirmationModal.open({ confirmationModal.open({
text: $t("settings.user.color.confirmation"), text: $t("settings.user.color.confirmation"),
onClosed: async () => { onClosed: async () => {

View File

@@ -90,7 +90,7 @@ const isAnimating = ref(true);
const animation = reactive({ const animation = reactive({
offsetY: SLIDE_OFFSET, offsetY: SLIDE_OFFSET,
opacity: 0, opacity: 0,
upArrowOffsetY: ARROW_IMAGE_HEIGHT, upArrowOffsetY: +ARROW_IMAGE_HEIGHT,
downArrowOffsetY: -ARROW_IMAGE_HEIGHT, downArrowOffsetY: -ARROW_IMAGE_HEIGHT,
}); });
@@ -125,7 +125,15 @@ const animateOutro = async () => {
.to(animation, { opacity: 0, duration: SLIDE_DURATION, ease: "none" }, 0); .to(animation, { opacity: 0, duration: SLIDE_DURATION, ease: "none" }, 0);
}; };
defineExpose({ animateIntro, animateOutro }); const showInstant = () => {
animation.offsetY = 0;
animation.opacity = 1;
animation.upArrowOffsetY = 0;
animation.downArrowOffsetY = 0;
isAnimating.value = false;
};
defineExpose({ animateIntro, animateOutro, showInstant });
const increase = () => { const increase = () => {
const newValue = value.value + 1; const newValue = value.value + 1;

View File

@@ -6,8 +6,13 @@ import StatusBar from "./StatusBar.vue";
import Notifications from "./Notifications.vue"; import Notifications from "./Notifications.vue";
const store = useSettingsStore(); const store = useSettingsStore();
const app = useAppStore();
onMounted(() => { onMounted(() => {
if (app.previousScreen === "achievements") {
store.isIntro = false;
return;
}
store.animateIntro(); store.animateIntro();
}); });
</script> </script>

View File

@@ -146,7 +146,7 @@ onMounted(async () => {
} }
}); });
useKeyDown(async ({ key, ndsButton, repeated }) => { useKeyDown(async ({ key, repeated }) => {
if (!repeated && key.toLocaleLowerCase() === "h") { if (!repeated && key.toLocaleLowerCase() === "h") {
if (app.hintsVisible) { if (app.hintsVisible) {
hideHelpLabels(); hideHelpLabels();

View File

@@ -13,43 +13,34 @@ export const ACHIEVEMENTS = [
// contact // contact
{ id: "contact_visit", secret: false }, { id: "contact_visit", secret: false },
{ id: "contact_git_visit", secret: false }, { id: "contact_git_visit", secret: false },
{ id: "contact_linkedin_visit", secret: false },
// settings // settings
{ id: "settings_color_change", secret: false }, { id: "settings_color_change", secret: false },
{ id: "settings_visit_all", secret: false },
// snake // snake
{ id: "snake_score_25", secret: false }, { id: "snake_score_25", secret: false },
// 2048 // 2048
{ id: "2048_score_512", secret: false }, { id: "2048_score_512", secret: false },
// taptap // taptap
{ id: "taptap_score_20", secret: false }, { id: "taptap_score_20", secret: false },
// secrets // meta
{ id: "settings_color_try_all", secret: true }, { id: "all_achievements", secret: false },
{ id: "settings_visit_all", secret: true },
{ id: "contact_36_notifications", secret: true },
] as const; ] as const;
export type Achievement = (typeof ACHIEVEMENTS)[number]["id"]; export type Achievement = (typeof ACHIEVEMENTS)[number]["id"];
export const useAchievementsStore = defineStore("achievements", () => { export const useAchievementsStore = defineStore("achievements", () => {
const app = useAppStore();
const storage = useLocalStorage( const storage = useLocalStorage(
STORAGE_ID, STORAGE_ID,
{ {
unlocked: [] as Achievement[], unlocked: [] as Achievement[],
advancement: { advancement: {
colors: [app.color.hex],
visitedSettings: [] as string[], visitedSettings: [] as string[],
}, },
}, },
{ mergeDefaults: true }, { mergeDefaults: true },
); );
if (!storage.value.advancement.colors.includes(app.color.hex)) {
storage.value.advancement.colors.push(app.color.hex);
}
const confetti = useConfetti();
const unlock = (name: Achievement) => { const unlock = (name: Achievement) => {
if (storage.value.unlocked.includes(name)) { if (storage.value.unlocked.includes(name)) {
return false; return false;
@@ -57,13 +48,14 @@ export const useAchievementsStore = defineStore("achievements", () => {
storage.value.unlocked.push(name); storage.value.unlocked.push(name);
const { assets } = useAssets(); if (name !== "all_achievements") {
assets.audio.messageReceived.play(0.5); const othersCount = ACHIEVEMENTS.length - 1;
const unlockedOthers = storage.value.unlocked.filter(
if (storage.value.unlocked.length === ACHIEVEMENTS.length) { (id) => id !== "all_achievements" && validIds.has(id as Achievement),
confetti.spawn(); ).length;
} else { if (unlockedOthers === othersCount) {
confetti.spawn(50, 175); unlock("all_achievements");
}
} }
return true; return true;
@@ -73,18 +65,20 @@ export const useAchievementsStore = defineStore("achievements", () => {
storage.value = { storage.value = {
unlocked: [], unlocked: [],
advancement: { advancement: {
colors: [app.color.hex],
visitedSettings: [], visitedSettings: [],
}, },
}; };
}; };
const validIds = new Set(ACHIEVEMENTS.map((a) => a.id));
const unlocked = computed(() =>
storage.value.unlocked.filter((id) => validIds.has(id as Achievement)),
);
return { return {
unlocked: computed(() => storage.value.unlocked), unlocked,
advancement: computed(() => storage.value.advancement), advancement: computed(() => storage.value.advancement),
allObtained: computed( allObtained: computed(() => unlocked.value.length === ACHIEVEMENTS.length),
() => storage.value.unlocked.length === ACHIEVEMENTS.length,
),
unlock, unlock,
reset, reset,
isUnlocked: computed( isUnlocked: computed(

View File

@@ -70,6 +70,7 @@ export const useAppStore = defineStore("app", {
this.ready = mode === "2d"; this.ready = mode === "2d";
this.settings.renderingMode = mode; this.settings.renderingMode = mode;
this.lagDetected = false; this.lagDetected = false;
if (mode === "2d") this.camera = null;
this.save(); this.save();
}, },

View File

@@ -86,10 +86,6 @@ export const useContactStore = defineStore("contact", {
{ notificationsYOffset: 0, duration: 0.075 }, { notificationsYOffset: 0, duration: 0.075 },
); );
if (this.notifications.length === 36) {
const achievements = useAchievementsStore();
achievements.unlock("contact_36_notifications");
}
}, },
animateOutro() { animateOutro() {

View File

@@ -58,6 +58,8 @@ export const useSettingsStore = defineStore("settings", {
isIntro: true, isIntro: true,
isOutro: false, isOutro: false,
returningFromAchievements: false,
}), }),
actions: { actions: {

View File

@@ -64,9 +64,9 @@
"snake_score_25": "Score 25 points\nin Snake", "snake_score_25": "Score 25 points\nin Snake",
"2048_score_512": "Reach the 512 tile\nin 2048", "2048_score_512": "Reach the 512 tile\nin 2048",
"taptap_score_20": "Score 20 points\nin TapTap", "taptap_score_20": "Score 20 points\nin TapTap",
"settings_color_try_all": "Try all colors",
"settings_visit_all": "Visit all settings\nsubmenus", "settings_visit_all": "Visit all settings\nsubmenus",
"contact_36_notifications": "Trigger 36\nnotifications" "contact_linkedin_visit": "Visit my LinkedIn\nprofile",
"all_achievements": "Unlock all\nachievements"
}, },
"intro": { "intro": {
"copyright": "WARNING - COPYRIGHT", "copyright": "WARNING - COPYRIGHT",

View File

@@ -64,9 +64,9 @@
"snake_score_25": "Marquer 25 points\nà Snake", "snake_score_25": "Marquer 25 points\nà Snake",
"2048_score_512": "Atteindre la tuile 512\nà 2048", "2048_score_512": "Atteindre la tuile 512\nà 2048",
"taptap_score_20": "Marquer 20 points\nà TapTap", "taptap_score_20": "Marquer 20 points\nà TapTap",
"settings_color_try_all": "Essayer toutes les\ncouleurs",
"settings_visit_all": "Visiter tous les\nparamètres", "settings_visit_all": "Visiter tous les\nparamètres",
"contact_36_notifications": "Déclencher 36\nnotifications" "contact_linkedin_visit": "Visiter mon profil\nLinkedIn",
"all_achievements": "Débloquer tous\nles succès"
}, },
"intro": { "intro": {
"copyright": "AVERTISSEMENT - COPYRIGHT", "copyright": "AVERTISSEMENT - COPYRIGHT",