Feat landing page - Mobile Optimisation (#112)

This optimisation is for mobile usage and also make header a better
component.
- Header Restruction
- Menu pop up when using smaller screen
- Seperate header components and other components
This commit is contained in:
Annie Huang authored and GitHub committed 2024-04-07 23:49:53 +08:00
commit 580d5fc3f8
17 files changed
+322 -162

No files matched your search

@@ -2,6 +2,6 @@
<font-awesome-icon :icon="['fas', 'list']" class="text-text-primary p-1" />
<h1 id="CommandBook" class="inline text-text-primary font-xl">指令之書</h1>
<div id="cheatsheets">
<p class="text-text-secondary">--指令列表--</p>
<p class="text-text-secondary">-- --指令列表-- --</p>
</div>
</template>
@@ -4,7 +4,7 @@ import { useToast } from 'vue-toastification'
import { ref } from 'vue'
import { Vue3Lottie } from 'vue3-lottie'
// import { surveyUrl } from '../config'
import CompleteAnimation from '../img/complete-animation.json'
import CompleteAnimation from '../../img/complete-animation.json'
const showModal = ref(true) // Controls whether the modal is visible or not
const router = useRouter()
@@ -26,7 +26,6 @@ const backtoMap = async () => {
try {
router.push({ name: 'map' })
} catch (err) {
// useToast().error('Failed to load map')
toast.error('無法讀取地圖')
console.error(err)
}
@@ -60,17 +59,6 @@ setTimeout(() => {
<p class="text-base text-text text-center">
接下來回到地圖,迎接新的挑戰吧!
</p>
<!-- <p class="text-base text-text">
您已成功完成挑戰,真的非常感謝您的參與!<br />
如果您能撥冗填寫這份問卷,我們將不勝感激,這對我們非常重要!
</p> -->
<!-- <a
id="SurveyButton"
:href="surveyUrl"
target="_blank"
class="inline-flex justify-center rounded-lg text-base font-black py-2 mt-3 bg-text-primary w-full"
>填寫問卷</a
> -->
<a
id="BacktoMap"
target="_blank"
@@ -1,8 +1,8 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useToast } from 'vue-toastification'
import { createSession } from '../store/session'
import { surveyUrl } from '../config'
import { createSession } from '../../store/session'
import { surveyUrl } from '../../config'
const router = useRouter()
const reset = async () => {
@@ -20,29 +20,31 @@ const continuePlay = async () => {
</script>
<template>
<section class="h-[10%] p-3 grid grid-cols-4 gap-4 place-content-between">
<section
class="h-fit w-fit px-1 py-1 grid grid-cols-4 gap-2 place-content-between"
>
<button id="survey" class="bg-bg-secondary rounded-lg p-2">
<a title="Survey" :href="surveyUrl" target="_blank" class="h-5 w-5">
<font-awesome-icon
:icon="['fas', 'file-invoice']"
class="text-text-primary"
class="text-text-primary px-1"
/>
<p class="text-text-primary inline ml-1">問卷</p>
<p class="text-text-primary inline ml-1 hidden md:flex">問卷</p>
</a>
</button>
<button disabled id="solution" class="bg-bg-disabled rounded-lg p-2">
<font-awesome-icon
:icon="['far', 'circle-question']"
class="text-text-disabled"
class="text-text-disabled px-1"
/>
<p class="text-text-disabled inline ml-1">解答</p>
<p class="text-text-disabled inline ml-1 hidden md:flex">解答</p>
</button>
<button id="reset" class="bg-bg-secondary rounded-lg p-2" @click="reset">
<font-awesome-icon
:icon="['fas', 'arrow-rotate-left']"
class="text-text-primary"
class="text-text-primary px-1"
/>
<p class="text-text-primary inline ml-1">重來</p>
<p class="text-text-primary inline ml-1 hidden md:flex">重來</p>
</button>
<button
id="continue"
@@ -51,9 +53,9 @@ const continuePlay = async () => {
>
<font-awesome-icon
:icon="['far', 'circle-right']"
class="text-text-primary"
class="text-text-primary px-1"
/>
<p class="text-text-primary inline ml-1">繼續</p>
<p class="text-text-primary inline ml-1 hidden md:flex">繼續</p>
</button>
</section>
</template>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import sessionStore from '../store/session'
import MarkdownText from './MarkdownText.vue'
import sessionStore from '../../store/session'
import MarkdownText from '../MarkdownText.vue'
const current = ref(-1)
watch(
@@ -27,16 +27,12 @@ const right = () => {
<template>
<div class="h-full flex flex-col">
<!-- Hint Header -->
<div class="flex items-center p-2 h-10">
<font-awesome-icon
:icon="['far', 'lightbulb']"
class="text-yellow-200 p-2 content-center"
/>
<h1 class="inline text-text w-1/2 font-xl p-2 m-1">
<!-- Hint -->
提示
</h1>
<h1 class="inline text-text w-1/2 font-xl p-2 m-1">提示</h1>
<div v-if="current !== -1" class="flex w-full justify-end items-end">
<button
class="p-2 m-1 w-1/8"
@@ -1,8 +1,8 @@
<script setup lang="ts">
import { computed } from 'vue'
import MarkdownText from './MarkdownText.vue'
import sessionStore from '../store/session'
import { Task } from '../types'
import MarkdownText from '../MarkdownText.vue'
import sessionStore from '../../store/session'
import { Task } from '../../types'
const tasks = computed(() => {
return sessionStore.session.tasks.map((task: Task) => {
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useTerminal } from '../store/session'
import { useTerminal } from '../../store/session'
const terminal = ref<HTMLElement | null>(null)
@@ -1,6 +1,6 @@
<script setup lang="ts">
import FileNode from './FileNode.vue'
import sessionStore from '../store/session'
import FileNode from '../FileNode.vue'
import sessionStore from '../../store/session'
</script>
<template>
@@ -0,0 +1,108 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useToast } from 'vue-toastification'
import { logout } from '../../utils/auth'
import { bugReportUrl, surveyUrl } from '../../config'
import { reset } from '../../store/session'
import userProfileStore, {
loadUserProfile,
resetUserProfile,
} from '../../store/userProfile'
const router = useRouter()
const toast = useToast()
const handleLogout = async () => {
try {
await logout()
resetUserProfile()
reset()
router.push({ name: 'login' })
} catch (err) {
toast.error('登出失敗')
console.error(err)
}
}
onMounted(async () => {
try {
await loadUserProfile()
} catch (err) {
toast.error('無法讀取使用者資料')
console.error(err)
}
})
</script>
<template>
<!-- <button id="warning" class="flex items-center h-5 w-5">
<font-awesome-icon
:icon="['fas', 'triangle-exclamation']"
class="text-text-primary h-full w-full"
/>
</button>
<button id="setting" class="flex items-center h-5 w-5">
<font-awesome-icon
:icon="['fas', 'gear']"
class="text-text-primary h-full w-full"
/>
</button>
<button id="fullscreen" class="flex items-center h-5 w-5">
<font-awesome-icon
:icon="['fas', 'expand']"
class="text-text-primary h-full w-full"
/>
</button> -->
<p
id="UsernameText"
class="text-text inline-block whitespace-nowrap px-1.5"
style="font-size: 2vh"
>
{{ userProfileStore.username }}
</p>
<a
title="Survey"
:href="surveyUrl"
target="_blank"
class="md:h-5 md:w-5 px-1.5 w-auto"
>
<font-awesome-icon
:icon="['fas', 'file-invoice']"
class="text-text-primary inline"
/>
<span class="text-text sm:hidden px-2">Survey</span>
</a>
<RouterLink
title="LeaderBoard"
to="/leaderboard"
class="md:h-5 md:w-5 px-1.5 w-auto"
>
<font-awesome-icon :icon="['fas', 'trophy']" class="text-text-primary" />
<span class="text-text sm:hidden px-2">Leader Board</span>
</RouterLink>
<RouterLink title="Map" to="/map" class="md:h-5 md:w-5 px-1.5 w-auto">
<font-awesome-icon :icon="['fas', 'map']" class="text-text-primary" />
<span class="text-text sm:hidden px-2">Map</span>
</RouterLink>
<a
title="Bug Report"
:href="bugReportUrl"
target="_blank"
class="md:h-5 md:w-5 px-1.5 w-auto"
>
<font-awesome-icon :icon="['fas', 'bug']" class="text-text-primary" />
<span class="text-text sm:hidden px-2">Bug Report</span>
</a>
<button
title="Sign Out"
@click="handleLogout"
class="md:h-5 md:w-5 px-1.5 w-auto"
>
<font-awesome-icon
:icon="['fas', 'arrow-right-from-bracket']"
class="text-text-primary"
/>
<span class="text-text sm:hidden px-2">Sign Out</span>
</button>
</template>
+65
View File
@@ -0,0 +1,65 @@
<script setup lang="ts">
import { defineProps, ref } from 'vue'
defineProps({
title: {
type: String,
required: false,
},
headerComponent: {
type: Object,
required: false,
},
})
const menuOpen = ref(false)
const windowWidth = window.innerWidth < 768
const toggleMenu = () => {
menuOpen.value = !menuOpen.value
}
</script>
<template>
<div
class="w-full top-0 px-5 fixed flex flex-row flex-wrap bg-bg-secondary justify-between items-center sm:justify-between"
>
<div class="flex flex-row items-center gap-3" :class="{ hidden: menuOpen }">
<img src="../../img/icon_totem.svg" class="h-8 justify-self-start" />
<RouterLink
to="/"
class="text-text-primary inline-block font-bold whitespace-nowrap pt-1 transition ease-in-out hover:scale-110 duration-300"
style="font-size: 2vh"
>
Linux Odyssey
</RouterLink>
<p
id="QuestTitle"
class="text-text inline-block font-bold whitespace-nowrap"
style="font-size: 2vh"
>
{{ title }}
</p>
</div>
<div
class="w-full flex flex-col items-center justify-end sm:w-auto sm:block sm:ml-6"
:class="{ hidden: !menuOpen }"
>
<component :is="headerComponent" />
</div>
<div
v-if="windowWidth"
:class="{ 'w-full flex justify-end pb-3': menuOpen }"
>
<button
type="button"
class="flex items-center h-5 w-5 rounded-md sm:hidden"
aria-controls="mobile-menu"
aria-expanded="false"
@click="toggleMenu"
>
<font-awesome-icon
:icon="['fas', 'bars']"
class="text-text-primary h-full w-full"
/>
</button>
</div>
</div>
</template>
@@ -0,0 +1,62 @@
<script setup lang="ts">
import { bugReportUrl } from '../../config'
const scrollToSection = (sectionId: string) => {
const section = document.getElementById(sectionId)
if (section) {
const container = document.querySelector('.overflow-y-auto') as HTMLElement // Replace '.overflow-y-auto' with the appropriate class or selector for your container
if (container) {
container.scrollTo({
top: section.offsetTop - container.offsetTop,
behavior: 'smooth', // Add smooth scrolling behavior if desired
})
}
}
}
</script>
<template>
<button
id="Intro"
class="text-text inline-block font-bold whitespace-nowrap pt-1 px-2 hover:text-text-primary"
@click="scrollToSection('intro')"
style="font-size: 2vh"
>
前言
</button>
<button
id="Feature"
class="text-text inline-block font-bold whitespace-nowrap pt-1 px-2 hover:text-text-primary"
@click="scrollToSection('feature')"
style="font-size: 2vh"
>
特色</button
><button
id="Info"
class="text-text inline-block font-bold whitespace-nowrap pt-1 px-2 hover:text-text-primary"
@click="scrollToSection('about')"
style="font-size: 2vh"
>
關於
</button>
<button
id="Contact"
class="text-text inline-block font-bold whitespace-nowrap pt-1 px-2 hover:text-text-primary"
@click="scrollToSection('contact')"
style="font-size: 2vh"
>
聯絡我們
</button>
<RouterLink
title="Sign In"
id="SignIn"
to="/register"
class="text-text inline-block font-bold whitespace-nowrap pt-1 px-2 hover:text-text-primary"
style="font-size: 2vh"
>
登入
</RouterLink>
<a title="Bug Report" :href="bugReportUrl" target="_blank" class="h-5 w-5">
<font-awesome-icon :icon="['fas', 'bug']" class="text-text-primary" />
</a>
</template>
+15 -11
View File
@@ -2,14 +2,15 @@
import { computed, onMounted } from 'vue'
import { useToast } from 'vue-toastification'
import sessionStore, { init } from '../store/session'
import GameHeaderPart from '../components/GameHeaderPart.vue'
import HeaderPart from '../components/header/HeaderPart.vue'
import GameHeaderComponents from '../components/header/GameHeaderComponents.vue'
// import CommandlistPart from '../components/CommandlistPart.vue'
import HintPart from '../components/HintPart.vue'
import QuestPart from '../components/QuestPart.vue'
import TerminalPart from '../components/TerminalPart.vue'
import VisualizationPart from '../components/VisualizationPart.vue'
import ControlPalette from '../components/ControlPalette.vue'
import CompleteModal from '../components/CompleteModal.vue'
import HintPart from '../components/game/HintPart.vue'
import QuestPart from '../components/game/QuestPart.vue'
import TerminalPart from '../components/game/TerminalPart.vue'
import VisualizationPart from '../components/game/VisualizationPart.vue'
import ControlPalette from '../components/game/ControlPalette.vue'
import CompleteModal from '../components/game/CompleteModal.vue'
import { LoadQuestError, LoadSessionError } from '../utils/errors'
const completed = computed(() => {
@@ -45,8 +46,11 @@ onMounted(async () => {
<template>
<!-- game header -->
<div class="h-[6vh] w-full">
<GameHeaderPart :title="sessionStore.quest?.title" />
<div class="w-full">
<HeaderPart
:title="sessionStore.quest?.title"
:headerComponent="GameHeaderComponents"
/>
</div>
<!-- current status indicator -->
<input
@@ -55,7 +59,7 @@ onMounted(async () => {
:value="sessionStore.session.status"
/>
<!-- main -->
<div id="main" class="h-[94vh] w-full flex p-3 space-x-3">
<div id="main" class="h-full pt-10 w-full flex p-3 space-x-3">
<!-- Topic and Command List -->
<div class="bg-bg-secondary h-full w-1/4 rounded-lg">
<section id="quest" class="h-full p-3 overflow-auto">
@@ -67,7 +71,7 @@ onMounted(async () => {
</section> -->
</div>
<!-- Terminal and Hint -->
<div class="bg-bg h-full w-1/2 rounded-lg">
<div class="bg-bg h-full rounded-lg w-1/2">
<section id="hint" class="h-3/5">
<HintPart />
</section>
+27 -101
View File
@@ -1,97 +1,19 @@
<script setup lang="ts">
import ContactButton from '../components/ContactButton.vue'
import DynamicBackground from '../components/DynamicBackground.vue'
import { bugReportUrl, facebookUrl, githubUrl } from '../config'
import LandingHeaderComponents from '../components/header/LandingHeaderComponents.vue'
import HeaderPart from '../components/header/HeaderPart.vue'
const scrollToSection = (sectionId: string) => {
const section = document.getElementById(sectionId)
if (section) {
const container = document.querySelector('.overflow-y-auto') // Replace '.overflow-y-auto' with the appropriate class or selector for your container
if (container) {
container.scrollTo({
top: section.offsetTop,
behavior: 'smooth', // Add smooth scrolling behavior if desired
})
}
}
}
import { facebookUrl, githubUrl } from '../config'
</script>
<template>
<div class="w-screen h-screen bg-scroll bg-bg">
<DynamicBackground class="w-full h-full" />
<div class="h-screen w-screen absolute top-0 left-0">
<!-- game header -->
<div
class="w-full h-[6vh] px-5 flex flex-row items-center justify-between bg-bg-secondary"
>
<div class="flex flex-row items-center gap-3">
<img src="../img/icon_totem.svg" class="h-8 justify-self-start" />
<button
id="GameTitle"
class="text-text-primary inline-block font-bold whitespace-nowrap pt-1"
style="font-size: 2vh"
>
Linux Odyssey
</button>
</div>
<div class="flex flex-row items-center gap-3">
<button
id="Intro"
class="text-text inline-block font-bold whitespace-nowrap pt-1"
@click="scrollToSection('intro')"
style="font-size: 2vh"
>
Intro
</button>
<button
id="Feature"
class="text-text inline-block font-bold whitespace-nowrap pt-1"
@click="scrollToSection('feature')"
style="font-size: 2vh"
>
Feature</button
><button
id="Info"
class="text-text inline-block font-bold whitespace-nowrap pt-1"
@click="scrollToSection('about')"
style="font-size: 2vh"
>
About
</button>
<button
id="Contact"
class="text-text inline-block font-bold whitespace-nowrap pt-1"
@click="scrollToSection('contact')"
style="font-size: 2vh"
>
Contact Us
</button>
<RouterLink
title="Sign In"
id="SignIn"
to="/register"
class="text-text inline-block font-bold whitespace-nowrap pt-1"
style="font-size: 2vh"
>
Sign In
</RouterLink>
<a
title="Bug Report"
:href="bugReportUrl"
target="_blank"
class="h-5 w-5"
>
<font-awesome-icon
:icon="['fas', 'bug']"
class="text-text-primary h-full w-full"
/>
</a>
</div>
</div>
<HeaderPart :headerComponent="LandingHeaderComponents" class="z-50" />
<!-- main content -->
<main class="w-full h-[94vh] overflow-y-auto">
<main class="w-full h-full overflow-y-auto">
<div class="w-full flex flex-col items-center">
<div
class="flex flex-col md:flex-row mt-10 justify-around gap-5 items-center my-10"
@@ -104,7 +26,6 @@ const scrollToSection = (sectionId: string) => {
Odyssey
</h1>
</div>
<!-- intro -->
<div class="w-full md:w-2/3 px-5">
<section
@@ -228,40 +149,43 @@ const scrollToSection = (sectionId: string) => {
鈦坦敏捷開發特別獎 第二名
</h1>
</div>
<!-- <h1
class="text-text-primary p-5 font-extrabold font-mono col-span-3 gap-3"
style="font-size: 3.5vh"
<div class="container mx-auto">
<div
class="justify-items-center grid grid-cols-1 sm:grid-cols-3 gap-5"
>
<h1
class="text-text-primary text-3xl font-extrabold font-mono sm:col-span-3 gap-6"
>
玩家感想
<!--可以做成翻頁卡牌一次一個-->
</h1>
<h1 class="text-text p-3" style="font-size: 2.5vh">
"很好玩很像益智遊戲"
</h1>
<h1 class="text-text p-3" style="font-size: 2.5vh">
<h1 class="text-xl text-center">"很好玩很像益智遊戲"</h1>
<h1 class="text-xl text-center">
"我從來不知道自由軟體的存在,謝謝你們。"
</h1>
<h1 class="text-text p-3" style="font-size: 2.5vh">
<h1 class="text-xl text-center">
"還蠻好玩的,讓程式變好玩"
</h1>
<h1 class="text-text p-3" style="font-size: 2.5vh">
<h1 class="text-xl text-center">
"以前不喜歡電腦課現在覺得喜歡一點點"
</h1>
<h1 class="text-text p-3" style="font-size: 2.5vh">
<h1 class="text-xl text-center">
"程式真的對新手很友善,讓我學到了很多東西"
</h1>
<h1 class="text-text p-3" style="font-size: 2.5vh">
<h1 class="text-xl text-center">
"感覺做得很好很簡明易懂關卡設計很用心"
</h1>
<h1 class="text-text p-3" style="font-size: 2.5vh">
<h1 class="text-xl text-center">
"這個體驗我覺得非常有趣,可以體驗到沒辦法體驗的程式,學長也都熱心指導,非常感謝學長的用心的講解,希望以後還可以再體驗這種活動"
</h1>
<h1 class="text-text p-3" style="font-size: 2.5vh">
<h1 class="text-xl text-center">
"以前我覺得寫程式很難不過現在覺得很有趣"
</h1>
<h1 class="text-text p-3" style="font-size: 2.5vh">
<h1 class="text-xl text-center">
"以前覺得寫程式是我生活中碰不到而且很難的項目。但通過這堂課之後,覺得寫程式不再那麼難以親近"
</h1> -->
</h1>
</div>
</div>
</section>
</div>
<section
@@ -271,7 +195,9 @@ const scrollToSection = (sectionId: string) => {
<h1 class="text-text-primary font-extrabold font-mono text-3xl">
聯絡我們
</h1>
<div class="flex flex-row items-center justify-around mt-5 gap-5">
<div
class="flex flex-row items-center justify-around mt-5 gap-5 mb-5"
>
<ContactButton
:url="facebookUrl"
:icons="['fab', 'facebook']"
+4 -3
View File
@@ -2,7 +2,8 @@
import { ref, onMounted } from 'vue'
import { useToast } from 'vue-toastification'
import DynamicBackground from '../components/DynamicBackground.vue'
import GameHeaderPart from '../components/GameHeaderPart.vue'
import HeaderPart from '../components/header/HeaderPart.vue'
import GameHeaderComponents from '../components/header/GameHeaderComponents.vue'
import api from '../utils/api'
const leaderboard = ref<
@@ -35,8 +36,8 @@ onMounted(async () => {
<div
class="w-screen h-screen absolute top-0 left-0 flex flex-wrap justify-center content-center"
>
<div class="h-[6vh] w-full">
<GameHeaderPart title="排行榜" />
<div class="w-full">
<HeaderPart title="排行榜" :headerComponent="GameHeaderComponents" />
</div>
<div class="w-fit h-[94vh] m-3">
<div
+4 -2
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import AuthForm from '../components/AuthForm.vue'
import Background from '../components/DynamicBackground.vue'
import DynamicBackground from '../components/DynamicBackground.vue'
import HeaderPart from '../components/header/HeaderPart.vue'
import {
TooManyRequestsError,
@@ -45,10 +46,11 @@ const handleLogin = async ({
<template>
<div class="w-screen h-screen">
<Background class="w-full h-full" />
<DynamicBackground class="w-full h-full" />
<div
class="w-screen h-screen absolute top-0 left-0 flex flex-wrap justify-center content-center"
>
<HeaderPart />
<div class="w-fit">
<AuthForm @onSubmit="handleLogin" type="login" />
</div>
+8 -3
View File
@@ -1,11 +1,16 @@
<script setup lang="ts">
import QuestMap from '../components/QuestMap.vue'
import GameHeaderPart from '../components/GameHeaderPart.vue'
import HeaderPart from '../components/header/HeaderPart.vue'
import GameHeaderComponents from '../components/header/GameHeaderComponents.vue'
</script>
<template>
<div class="h-[6vh] w-full">
<GameHeaderPart title="關卡地圖" />
<div class="w-fit">
<HeaderPart
title="關卡地圖"
:headerComponent="GameHeaderComponents"
class="z-50"
/>
</div>
<div class="flex flex-col justify-center items-center w-full h-[94vh]">
<QuestMap />
+5 -4
View File
@@ -3,8 +3,8 @@ import validator from 'validator'
import { isValidUsername } from '@linux-odyssey/utils'
import { passwordPolicy } from '@linux-odyssey/constants'
import AuthForm from '../components/AuthForm.vue'
import Background from '../components/DynamicBackground.vue'
import DynamicBackground from '../components/DynamicBackground.vue'
import HeaderPart from '../components/header/HeaderPart.vue'
import { checkUsername, register } from '../utils/auth'
import {
UnauthorizedError,
@@ -89,11 +89,12 @@ async function check({
<template>
<div class="w-screen h-screen">
<Background class="w-full h-full" />
<DynamicBackground class="w-full h-full" />
<div
class="h-screen w-screen absolute top-0 left-0 flex flex-wrap justify-center content-center"
>
<div class="w-fit">
<HeaderPart />
<div class="w-fit mt-8">
<AuthForm
@onSubmit="handleRegister"
@onChange="check"