Compare commits
6 Commits
da57605de9
...
d4e002a936
| Author | SHA1 | Date | |
|---|---|---|---|
| d4e002a936 | |||
| 5c0014ca61 | |||
| 085e5f20a9 | |||
| 0d580e7edf | |||
| fa73841098 | |||
| 623401ca36 |
Binary file not shown.
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
@@ -6,6 +6,8 @@ const { onRender } = useScreen();
|
||||
const achievements = useAchievementsStore();
|
||||
const { assets } = useAssets();
|
||||
|
||||
const confetti = useConfetti();
|
||||
|
||||
const queue = ref<Achievement[]>([]);
|
||||
const currentAchievement = ref<Achievement | null>(null);
|
||||
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 LINE_HEIGHT = 8;
|
||||
|
||||
achievements.$onAction(({ name, args, after }) => {
|
||||
if (name === "unlock") {
|
||||
after((wasUnlocked) => {
|
||||
if (wasUnlocked) {
|
||||
queue.value.push(args[0]);
|
||||
processQueue();
|
||||
watch(
|
||||
() => achievements.unlocked,
|
||||
(newVal, oldVal) => {
|
||||
const added = newVal.filter((id) => !oldVal?.includes(id));
|
||||
for (const id of added) {
|
||||
queue.value.push(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (added.length > 0) processQueue();
|
||||
},
|
||||
);
|
||||
|
||||
const processQueue = () => {
|
||||
if (isAnimating.value || queue.value.length === 0) return;
|
||||
@@ -40,6 +42,14 @@ const processQueue = () => {
|
||||
currentAchievement.value = next;
|
||||
isAnimating.value = true;
|
||||
|
||||
assets.audio.messageReceived.play(0.5);
|
||||
|
||||
if (next === "all_achievements") {
|
||||
confetti.spawn();
|
||||
} else {
|
||||
confetti.spawn(50, 175);
|
||||
}
|
||||
|
||||
gsap
|
||||
.timeline()
|
||||
.to(x, {
|
||||
|
||||
@@ -100,6 +100,8 @@ const handleActivateA = async (button: typeof selected.value) => {
|
||||
|
||||
if (button === "git") {
|
||||
achievements.unlock("contact_git_visit");
|
||||
} else if (button === "linkedin") {
|
||||
achievements.unlock("contact_linkedin_visit");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -199,7 +199,7 @@ watch(
|
||||
|
||||
const { onRender, onBeforeRender } = useLoop();
|
||||
|
||||
const LAG_FPS_THRESHOLD = 40;
|
||||
const LAG_FPS_THRESHOLD = 30;
|
||||
const LAG_DURATION_SECS = 5;
|
||||
let lagSeconds = 0;
|
||||
let lagCheckDone = false;
|
||||
@@ -352,7 +352,7 @@ onBeforeRender(() => {
|
||||
const delta = (now - lastFrameTime) / 1000;
|
||||
lastFrameTime = now;
|
||||
|
||||
if (document.hidden || delta > 0.5) {
|
||||
if (document.hidden || delta > 0.5 || !app.booted) {
|
||||
lagSeconds = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -27,16 +27,17 @@ const achievementAssets =
|
||||
assets.images.settings.bottomScreen.clock.achievements;
|
||||
const { onRender, onClick } = useScreen();
|
||||
|
||||
const app = useAppStore();
|
||||
const store = useSettingsStore();
|
||||
const app = useAppStore();
|
||||
const achievements = useAchievementsStore();
|
||||
const achievementsScreen = useAchievementsScreen();
|
||||
const confirmationModal = useConfirmationModal();
|
||||
|
||||
const isAnimating = ref(true);
|
||||
const comingFromAchievements = store.returningFromAchievements;
|
||||
|
||||
const bLabel = ref($t("common.goBack"));
|
||||
const aLabel = ref($t("common.select"));
|
||||
const isAnimating = ref(!comingFromAchievements);
|
||||
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_DURATION = 0.25;
|
||||
@@ -45,9 +46,9 @@ const ARROW_SLIDE_DURATION = 0.167;
|
||||
const VIEW_ALL_OFFSET = -20;
|
||||
|
||||
const animation = reactive({
|
||||
offsetY: SLIDE_OFFSET,
|
||||
opacity: 0,
|
||||
viewAllOffsetY: VIEW_ALL_OFFSET,
|
||||
offsetY: comingFromAchievements ? 0 : SLIDE_OFFSET,
|
||||
opacity: comingFromAchievements ? 1 : 0,
|
||||
viewAllOffsetY: comingFromAchievements ? 0 : VIEW_ALL_OFFSET,
|
||||
});
|
||||
|
||||
const obtainedRef =
|
||||
@@ -106,6 +107,12 @@ const animateOutro = async () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
store.returningFromAchievements = false;
|
||||
if (comingFromAchievements) {
|
||||
obtainedRef.value?.showInstant();
|
||||
totalRef.value?.showInstant();
|
||||
return;
|
||||
}
|
||||
animateIntro();
|
||||
});
|
||||
|
||||
@@ -132,6 +139,7 @@ const handleReset = () => {
|
||||
const handleVisitAll = () => {
|
||||
if (isAnimating.value) return;
|
||||
assets.audio.menuOpen.play();
|
||||
store.returningFromAchievements = true;
|
||||
achievementsScreen.animateFadeToBlackIntro();
|
||||
};
|
||||
|
||||
@@ -164,7 +172,7 @@ onRender((ctx) => {
|
||||
? APP_COLOR_TO_FONT_COLOR[app.color.hex]!
|
||||
: "#fbfbfb";
|
||||
ctx.fillText("/", 7 * 16 + 3, 4 * 16 + 4 + 39);
|
||||
});
|
||||
}, 5);
|
||||
|
||||
onRender((ctx) => {
|
||||
ctx.translate(0, animation.viewAllOffsetY);
|
||||
|
||||
@@ -35,7 +35,9 @@ const animation = reactive({
|
||||
isIntro: true,
|
||||
isOutro: false,
|
||||
});
|
||||
const isAnimating = computed(() => animation.isIntro || animation.isOutro);
|
||||
const isAnimating = computed(
|
||||
() => animation.isIntro || animation.isOutro || confirmationModal.isOpen,
|
||||
);
|
||||
|
||||
const animateIntro = (): gsap.core.Timeline => {
|
||||
animation.isIntro = true;
|
||||
@@ -289,13 +291,6 @@ const handleActivateA = () => {
|
||||
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({
|
||||
text: $t("settings.user.color.confirmation"),
|
||||
onClosed: async () => {
|
||||
|
||||
@@ -90,7 +90,7 @@ const isAnimating = ref(true);
|
||||
const animation = reactive({
|
||||
offsetY: SLIDE_OFFSET,
|
||||
opacity: 0,
|
||||
upArrowOffsetY: ARROW_IMAGE_HEIGHT,
|
||||
upArrowOffsetY: +ARROW_IMAGE_HEIGHT,
|
||||
downArrowOffsetY: -ARROW_IMAGE_HEIGHT,
|
||||
});
|
||||
|
||||
@@ -125,7 +125,15 @@ const animateOutro = async () => {
|
||||
.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 newValue = value.value + 1;
|
||||
|
||||
@@ -6,8 +6,13 @@ import StatusBar from "./StatusBar.vue";
|
||||
import Notifications from "./Notifications.vue";
|
||||
|
||||
const store = useSettingsStore();
|
||||
const app = useAppStore();
|
||||
|
||||
onMounted(() => {
|
||||
if (app.previousScreen === "achievements") {
|
||||
store.isIntro = false;
|
||||
return;
|
||||
}
|
||||
store.animateIntro();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -146,7 +146,7 @@ onMounted(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
useKeyDown(async ({ key, ndsButton, repeated }) => {
|
||||
useKeyDown(async ({ key, repeated }) => {
|
||||
if (!repeated && key.toLocaleLowerCase() === "h") {
|
||||
if (app.hintsVisible) {
|
||||
hideHelpLabels();
|
||||
|
||||
@@ -13,43 +13,34 @@ export const ACHIEVEMENTS = [
|
||||
// contact
|
||||
{ id: "contact_visit", secret: false },
|
||||
{ id: "contact_git_visit", secret: false },
|
||||
{ id: "contact_linkedin_visit", secret: false },
|
||||
// settings
|
||||
{ id: "settings_color_change", secret: false },
|
||||
{ id: "settings_visit_all", secret: false },
|
||||
// snake
|
||||
{ id: "snake_score_25", secret: false },
|
||||
// 2048
|
||||
{ id: "2048_score_512", secret: false },
|
||||
// taptap
|
||||
{ id: "taptap_score_20", secret: false },
|
||||
// secrets
|
||||
{ id: "settings_color_try_all", secret: true },
|
||||
{ id: "settings_visit_all", secret: true },
|
||||
{ id: "contact_36_notifications", secret: true },
|
||||
// meta
|
||||
{ id: "all_achievements", secret: false },
|
||||
] as const;
|
||||
|
||||
export type Achievement = (typeof ACHIEVEMENTS)[number]["id"];
|
||||
|
||||
export const useAchievementsStore = defineStore("achievements", () => {
|
||||
const app = useAppStore();
|
||||
|
||||
const storage = useLocalStorage(
|
||||
STORAGE_ID,
|
||||
{
|
||||
unlocked: [] as Achievement[],
|
||||
advancement: {
|
||||
colors: [app.color.hex],
|
||||
visitedSettings: [] as string[],
|
||||
},
|
||||
},
|
||||
{ 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) => {
|
||||
if (storage.value.unlocked.includes(name)) {
|
||||
return false;
|
||||
@@ -57,13 +48,14 @@ export const useAchievementsStore = defineStore("achievements", () => {
|
||||
|
||||
storage.value.unlocked.push(name);
|
||||
|
||||
const { assets } = useAssets();
|
||||
assets.audio.messageReceived.play(0.5);
|
||||
|
||||
if (storage.value.unlocked.length === ACHIEVEMENTS.length) {
|
||||
confetti.spawn();
|
||||
} else {
|
||||
confetti.spawn(50, 175);
|
||||
if (name !== "all_achievements") {
|
||||
const othersCount = ACHIEVEMENTS.length - 1;
|
||||
const unlockedOthers = storage.value.unlocked.filter(
|
||||
(id) => id !== "all_achievements" && validIds.has(id as Achievement),
|
||||
).length;
|
||||
if (unlockedOthers === othersCount) {
|
||||
unlock("all_achievements");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -73,18 +65,20 @@ export const useAchievementsStore = defineStore("achievements", () => {
|
||||
storage.value = {
|
||||
unlocked: [],
|
||||
advancement: {
|
||||
colors: [app.color.hex],
|
||||
visitedSettings: [],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const validIds = new Set(ACHIEVEMENTS.map((a) => a.id));
|
||||
const unlocked = computed(() =>
|
||||
storage.value.unlocked.filter((id) => validIds.has(id as Achievement)),
|
||||
);
|
||||
|
||||
return {
|
||||
unlocked: computed(() => storage.value.unlocked),
|
||||
unlocked,
|
||||
advancement: computed(() => storage.value.advancement),
|
||||
allObtained: computed(
|
||||
() => storage.value.unlocked.length === ACHIEVEMENTS.length,
|
||||
),
|
||||
allObtained: computed(() => unlocked.value.length === ACHIEVEMENTS.length),
|
||||
unlock,
|
||||
reset,
|
||||
isUnlocked: computed(
|
||||
|
||||
@@ -70,6 +70,7 @@ export const useAppStore = defineStore("app", {
|
||||
this.ready = mode === "2d";
|
||||
this.settings.renderingMode = mode;
|
||||
this.lagDetected = false;
|
||||
if (mode === "2d") this.camera = null;
|
||||
this.save();
|
||||
},
|
||||
|
||||
|
||||
@@ -86,10 +86,6 @@ export const useContactStore = defineStore("contact", {
|
||||
{ notificationsYOffset: 0, duration: 0.075 },
|
||||
);
|
||||
|
||||
if (this.notifications.length === 36) {
|
||||
const achievements = useAchievementsStore();
|
||||
achievements.unlock("contact_36_notifications");
|
||||
}
|
||||
},
|
||||
|
||||
animateOutro() {
|
||||
|
||||
@@ -58,6 +58,8 @@ export const useSettingsStore = defineStore("settings", {
|
||||
|
||||
isIntro: true,
|
||||
isOutro: false,
|
||||
|
||||
returningFromAchievements: false,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
"snake_score_25": "Score 25 points\nin Snake",
|
||||
"2048_score_512": "Reach the 512 tile\nin 2048",
|
||||
"taptap_score_20": "Score 20 points\nin TapTap",
|
||||
"settings_color_try_all": "Try all colors",
|
||||
"settings_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": {
|
||||
"copyright": "WARNING - COPYRIGHT",
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
"snake_score_25": "Marquer 25 points\nà Snake",
|
||||
"2048_score_512": "Atteindre la tuile 512\nà 2048",
|
||||
"taptap_score_20": "Marquer 20 points\nà TapTap",
|
||||
"settings_color_try_all": "Essayer toutes les\ncouleurs",
|
||||
"settings_visit_all": "Visiter tous les\nparamètres",
|
||||
"contact_36_notifications": "Déclencher 36\nnotifications"
|
||||
"contact_linkedin_visit": "Visiter mon profil\nLinkedIn",
|
||||
"all_achievements": "Débloquer tous\nles succès"
|
||||
},
|
||||
"intro": {
|
||||
"copyright": "AVERTISSEMENT - COPYRIGHT",
|
||||
|
||||
Reference in New Issue
Block a user