Skip to content

Commit cc5568a

Browse files
committed
feat: implement responsive layout and scaling for player and selector views
1 parent 05e9941 commit cc5568a

4 files changed

Lines changed: 161 additions & 17 deletions

File tree

src/App.vue

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,110 @@
11
<script setup lang="ts">
2-
import { RouterView } from 'vue-router'
2+
import { RouterView, useRoute } from 'vue-router'
3+
import { ref, onMounted, onUnmounted, watch } from 'vue'
4+
5+
const route = useRoute()
6+
const wrapperStyle = ref<Record<string, string>>({})
7+
8+
const updateLayout = () => {
9+
const width = window.innerWidth
10+
const height = window.innerHeight
11+
const isPortrait = height > width
12+
const isPlayer = route.name === 'player'
13+
14+
const style: Record<string, string> = {
15+
position: 'absolute',
16+
top: '50%',
17+
left: '50%',
18+
transformOrigin: 'center',
19+
backgroundColor: 'var(--color-background)',
20+
boxSizing: 'border-box'
21+
}
22+
23+
if (isPlayer) {
24+
// Force Landscape for Player
25+
style.overflow = 'hidden'
26+
if (isPortrait) {
27+
style.width = `${height}px`
28+
style.height = `${width}px`
29+
style.transform = 'translate(-50%, -50%) rotate(90deg)'
30+
} else {
31+
style.width = `${width}px`
32+
style.height = `${height}px`
33+
style.transform = 'translate(-50%, -50%)'
34+
}
35+
} else {
36+
// Normal Layout for Selector/Home
37+
style.width = '100%'
38+
style.height = '100%'
39+
style.transform = 'translate(-50%, -50%)'
40+
style.overflow = 'auto'
41+
}
42+
43+
wrapperStyle.value = style
44+
}
45+
46+
let resizeTimeout: number | null = null
47+
const onResize = () => {
48+
if (resizeTimeout) window.clearTimeout(resizeTimeout)
49+
// Immediate update
50+
updateLayout()
51+
// Delayed update to catch mobile browser UI changes
52+
resizeTimeout = window.setTimeout(updateLayout, 300)
53+
}
54+
55+
onMounted(() => {
56+
updateLayout()
57+
window.addEventListener('resize', onResize)
58+
})
59+
60+
onUnmounted(() => {
61+
window.removeEventListener('resize', onResize)
62+
if (resizeTimeout) window.clearTimeout(resizeTimeout)
63+
})
64+
65+
watch(
66+
() => route.name,
67+
() => {
68+
// Update layout when route changes (e.g. entering/leaving player)
69+
// Use nextTick or small timeout if needed, but usually immediate is fine for style update
70+
setTimeout(updateLayout, 50)
71+
}
72+
)
373
</script>
474

575
<template>
6-
<RouterView />
76+
<div class="app-wrapper" :style="wrapperStyle">
77+
<RouterView />
78+
</div>
779
</template>
880

981
<style>
10-
/* Global reset or basic styles */
82+
/* Global overrides */
1183
body {
1284
margin: 0;
1385
padding: 0;
86+
overflow: hidden !important;
87+
background-color: #000;
88+
display: block !important;
89+
width: 100vw;
90+
height: 100vh;
1491
font-family: sans-serif;
1592
}
93+
94+
#app {
95+
width: 100%;
96+
height: 100%;
97+
padding: 0 !important;
98+
margin: 0 !important;
99+
max-width: none !important;
100+
display: block !important;
101+
}
102+
103+
.app-wrapper {
104+
scrollbar-width: none;
105+
-ms-overflow-style: none;
106+
}
107+
.app-wrapper::-webkit-scrollbar {
108+
display: none;
109+
}
16110
</style>

src/assets/main.css

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
@import './base.css';
22

33
#app {
4-
max-width: 1280px;
5-
margin: 0 auto;
6-
padding: 2rem;
4+
width: 100%;
5+
height: 100%;
6+
margin: 0;
7+
padding: 0;
78
font-weight: normal;
89
}
910

src/views/PlayerView.vue

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { onMounted, watch, ref } from 'vue'
2+
import { onMounted, onUnmounted, watch, ref } from 'vue'
33
import { useRoute, useRouter } from 'vue-router'
44
import { useFgoStore } from '@/stores/fgo'
55
import { useScriptPlayer } from '@/composables/useScriptPlayer'
@@ -12,6 +12,27 @@ const player = useScriptPlayer()
1212
const debugMode = ref(true)
1313
const showSettings = ref(false)
1414
15+
const containerRef = ref<HTMLElement | null>(null)
16+
const gameScreenScale = ref(1)
17+
18+
const updateScale = () => {
19+
if (!containerRef.value) return
20+
const parentW = containerRef.value.clientWidth
21+
const parentH = containerRef.value.clientHeight
22+
23+
// If dimensions are 0 (e.g. hidden), skip
24+
if (parentW === 0 || parentH === 0) return
25+
26+
const targetW = 1024
27+
const targetH = 626
28+
29+
const scaleW = parentW / targetW
30+
const scaleH = parentH / targetH
31+
32+
// Fit inside (contain)
33+
gameScreenScale.value = Math.min(scaleW, scaleH)
34+
}
35+
1536
const loadScript = async () => {
1637
const questId = Number(route.params.questId)
1738
const phase = Number(route.params.phase)
@@ -29,8 +50,29 @@ const loadScript = async () => {
2950
}
3051
}
3152
53+
let resizeObserver: ResizeObserver | null = null
54+
3255
onMounted(() => {
3356
loadScript()
57+
58+
// Use ResizeObserver to detect container size changes
59+
if (containerRef.value) {
60+
resizeObserver = new ResizeObserver(() => {
61+
updateScale()
62+
})
63+
resizeObserver.observe(containerRef.value)
64+
}
65+
66+
window.addEventListener('resize', updateScale)
67+
// Initial scale update
68+
setTimeout(updateScale, 100)
69+
})
70+
71+
onUnmounted(() => {
72+
if (resizeObserver) {
73+
resizeObserver.disconnect()
74+
}
75+
window.removeEventListener('resize', updateScale)
3476
})
3577
3678
watch(
@@ -101,10 +143,15 @@ const exit = () => {
101143
</script>
102144

103145
<template>
104-
<div class="player-container">
146+
<div class="player-container" ref="containerRef">
105147
<div v-if="store.isLoading" class="loading">Loading Script...</div>
106148
<div v-else-if="store.error" class="error">{{ store.error }}</div>
107-
<div v-else-if="store.currentQuest" class="game-screen" @click="player.next()">
149+
<div
150+
v-else-if="store.currentQuest"
151+
class="game-screen"
152+
@click="player.next()"
153+
:style="{ transform: `scale(${gameScreenScale})` }"
154+
>
108155
<!-- Background Layer -->
109156
<div
110157
class="background"
@@ -175,33 +222,32 @@ const exit = () => {
175222

176223
<style scoped>
177224
.player-container {
178-
position: fixed;
225+
position: absolute;
179226
top: 0;
180227
left: 0;
181228
z-index: 1000;
182-
width: 100vw;
183-
height: 100vh;
229+
width: 100%;
230+
height: 100%;
184231
background: #000;
185232
color: white;
186233
display: flex;
187234
justify-content: center;
188235
align-items: center;
236+
overflow: hidden;
189237
}
190238
191239
.game-screen {
192240
position: relative;
193-
width: 100%;
194-
height: 100%;
195-
max-width: 1024px;
196-
max-height: 626px;
241+
width: 1024px;
242+
height: 626px;
197243
background: #333;
198244
overflow: hidden;
199245
cursor: pointer;
200-
aspect-ratio: 1024 / 626;
201246
user-select: none;
202247
-webkit-user-select: none;
203248
-moz-user-select: none;
204249
-ms-user-select: none;
250+
flex-shrink: 0;
205251
}
206252
207253
.background {

src/views/SelectorView.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ const getQuestTypeLabel = (type: string): string => {
195195
padding: 20px;
196196
max-width: 1000px;
197197
margin: 0 auto;
198+
height: 100%;
199+
overflow-y: auto;
200+
box-sizing: border-box;
198201
}
199202
200203
.header {

0 commit comments

Comments
 (0)