diff --git a/quiz-app2/src/App.vue b/quiz-app2/src/App.vue index e8f6e6b..50316ac 100644 --- a/quiz-app2/src/App.vue +++ b/quiz-app2/src/App.vue @@ -129,7 +129,6 @@ const compactTierTitle = (titleText: I18nText | string) => { let timerId: number | null = null; let startedAt = 0; -// --- i18n Dictionary --- const i18n = computed(() => { const isEn = selectedLanguage.value === "en"; return { @@ -161,12 +160,12 @@ const i18n = computed(() => { completedText: isEn ? "Completed" : "完了", specialUnlocked: isEn ? "Special Unlocked" : "スペシャル解放", allCorrectTitle: isEn - ? "Special Page Unlocked!" - : "スペシャルページ解放!", + ? "Present a souvenir!" + : "おみやげをプレゼント!", allCorrectDesc: isEn - ? "You have completed this set and unlocked the special page." - : "このセットを最後まで進めたため、スペシャルページへ進めます。", - goToSpecial: isEn ? "Go to Special Page" : "スペシャルページへ", + ? "Prepared some souvenirs for everyone played." + : "プレイしてくれたみんなのために記念品を用意したよ。", + goToSpecial: isEn ? "Go to Souvenirs Page" : "おみやげページ", nextLevel: isEn ? "Next Level" : "次のレベルへ", retryLevel: isEn ? isSingleTier @@ -199,6 +198,18 @@ const i18n = computed(() => { }; }); +watch(selectedLanguage, () => { + if ( + keywordMessage.value === "合言葉が違います。" || + keywordMessage.value === "Incorrect keyword." + ) { + keywordMessage.value = + selectedLanguage.value === "en" + ? "Incorrect keyword." + : "合言葉が違います。"; + } +}); + const correctFeedbackPhrases: I18nText[] = [ { ja: "その調子!", en: "Keep it up!" }, { ja: "素晴らしい!", en: "Excellent!" }, @@ -389,14 +400,14 @@ const shareText = computed(() => { const tierName = t(currentTier.value.title); const baseText = isEn - ? `Cleared ${isSingleTier ? "all stages" : tierName} on Go Conference 2026 CodeLab! Correct: ${runCorrectCount.value}/${tierSize.value} Score: ${runScore.value}` - : `Go Conference 2026 CodeLabで${isSingleTier ? "全問題を" : ` ${tierName} を`}クリアしました! 正解数: ${runCorrectCount.value}/${tierSize.value} スコア: ${runScore.value}`; + ? `Cleared ${isSingleTier ? "all stages" : tierName} on Go Conference 2026 CodeLab!\nCorrect: ${runCorrectCount.value}/${tierSize.value} Score: ${runScore.value}` + : `Go Conference 2026 CodeLabで${isSingleTier ? "全問題を" : ` ${tierName} を`}クリアしました!\n正解数: ${runCorrectCount.value}/${tierSize.value} スコア: ${runScore.value}`; const url = typeof window !== "undefined" ? window.location.href.split("#")[0].split("?")[0] : "https://gocon.jp/"; - return `${baseText}\n#gocon26cl\n${url}`; + return `${baseText}\n${url}\n#gocon26 #gocon26cl`; }); const xShareUrl = computed( @@ -571,6 +582,10 @@ const finishTierRun = () => { const goToNextStage = () => { if (!currentResult.value) return; + if (typeof window !== "undefined") { + window.scrollTo(0, 0); + } + if (currentQuestionIndex.value === tierSize.value - 1) { finishTierRun(); return; @@ -624,7 +639,10 @@ const closeKeywordModal = () => { const submitKeyword = () => { if (keywordValue.value.trim() !== PREVIEW_UNLOCK_KEYWORD) { - keywordMessage.value = i18n.value.keywordIncorrect; + keywordMessage.value = + selectedLanguage.value === "en" + ? "Incorrect keyword." + : "合言葉が違います。"; return; } diff --git a/quiz-app2/src/components/games/FillBlankTapGame.vue b/quiz-app2/src/components/games/FillBlankTapGame.vue index e32322b..560c549 100644 --- a/quiz-app2/src/components/games/FillBlankTapGame.vue +++ b/quiz-app2/src/components/games/FillBlankTapGame.vue @@ -65,8 +65,15 @@ const clearSelection = () => { const slotIsFilled = (item: PoolItem) => slots.value.some((slot) => slot?.id === item.id); -const addToken = (item: PoolItem) => { +const toggleToken = (item: PoolItem) => { if (props.locked) return; + + const existingIndex = slots.value.findIndex((s) => s?.id === item.id); + if (existingIndex !== -1) { + slots.value[existingIndex] = null; + return; + } + const index = slots.value.findIndex((s) => s === null); if (index !== -1) { slots.value[index] = item; @@ -124,6 +131,10 @@ watch(
@@ -171,8 +182,8 @@ watch( }} {{ locale === "en" - ? "Tap to insert from left" - : "タップで左から挿入" + ? "Tap to insert/remove" + : "タップで追加 / 解除" }}
@@ -181,9 +192,10 @@ watch( v-for="item in poolItems" :key="item.id" tone="secondary" - :disabled="slotIsFilled(item) || locked" - class="px-4 py-3 font-mono text-[13px]" - @click="addToken(item)" + :disabled="locked" + class="px-4 py-3 font-mono text-[13px] transition-all" + :class="{ 'opacity-30': slotIsFilled(item) }" + @click="toggleToken(item)" > {{ item.label }} diff --git a/quiz-app2/src/components/ui/StageOutputPanel.vue b/quiz-app2/src/components/ui/StageOutputPanel.vue index ed91f01..1c5fdce 100644 --- a/quiz-app2/src/components/ui/StageOutputPanel.vue +++ b/quiz-app2/src/components/ui/StageOutputPanel.vue @@ -2,11 +2,24 @@ import { inject, ref, type Ref } from "vue"; import type { Locale } from "../../types"; -defineProps<{ +const props = defineProps<{ lines: string[]; + isFill?: boolean; + slots?: any[]; + locked?: boolean; +}>(); + +const emit = defineEmits<{ + (e: "remove-token", index: number): void; }>(); const locale = inject("locale", ref("ja")) as Ref; + +// プレースホルダーの解析ロジック +const splitLine = (line: string) => line.split(/(\[\d+\])/).filter(Boolean); +const isSlot = (part: string) => /^\[\d+\]$/.test(part); +const slotIndex = (part: string) => + parseInt(part.replace(/[\[\]]/g, ""), 10) - 1;