style: mobile responsive

This commit is contained in:
Neko Ayaka
2024-12-03 23:08:04 +08:00
parent 5546d105f1
commit bc04dbaf2b
5 changed files with 98 additions and 26 deletions
+1
View File
@@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>アイリ</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0" />
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+1
View File
@@ -60,6 +60,7 @@
"devDependencies": {
"@airi-proj/elevenlabs": "workspace:^",
"@iconify-json/carbon": "^1.2.4",
"@iconify-json/eos-icons": "^1.2.1",
"@intlify/unplugin-vue-i18n": "^6.0.0",
"@shikijs/markdown-it": "^1.24.0",
"@types/markdown-it-link-attributes": "^3.0.5",
+40 -12
View File
@@ -2,7 +2,7 @@
import { Application } from '@pixi/app'
import { extensions } from '@pixi/extensions'
import { Ticker, TickerPlugin } from '@pixi/ticker'
import { useElementBounding, useWindowSize } from '@vueuse/core'
import { breakpointsTailwind, useBreakpoints, useElementBounding, useWindowSize } from '@vueuse/core'
import { Live2DModel, MotionPreloadStrategy, MotionPriority } from 'pixi-live2d-display/cubism4'
import { onMounted, onUnmounted, ref, watch } from 'vue'
@@ -21,10 +21,37 @@ const mouthOpenSize = computed(() => {
return Math.max(0, Math.min(100, props.mouthOpenSize))
})
const breakpoints = useBreakpoints(breakpointsTailwind)
const { width, height } = useWindowSize()
const containerElementBounding = useElementBounding(containerRef)
const containerParentElementBounding = useElementBounding(containerRef.value?.parentElement)
const isMobile = computed(() => breakpoints.between('sm', 'md').value || breakpoints.smaller('sm').value)
const isTablet = computed(() => breakpoints.between('md', 'lg').value)
const isDesktop = computed(() => breakpoints.greaterOrEqual('lg').value)
const canvasWidth = computed(() => {
if (isDesktop.value)
return containerElementBounding.width.value
else if (isMobile.value)
return (width.value - 16) // padding
else if (isTablet.value)
return (width.value - 16) // padding
else
return containerElementBounding.width.value
})
const canvasHeight = computed(() => {
if (isDesktop.value)
return Math.max(600, containerParentElementBounding.height.value)
else if (isMobile.value)
return 600
else if (isTablet.value)
return 600
else
return Math.max(600, containerParentElementBounding.height.value)
})
function getCoreModel() {
return model.value!.internalModel.coreModel as any
}
@@ -38,19 +65,20 @@ async function initLive2DPixiStage(parent: HTMLDivElement) {
extensions.add(TickerPlugin)
pixiApp.value = new Application({
width: containerElementBounding.width.value,
height: Math.max(600, containerParentElementBounding.height.value),
width: canvasWidth.value,
height: canvasHeight.value,
backgroundAlpha: 0,
})
pixiAppCanvas.value = pixiApp.value.view
pixiAppCanvas.value.style.objectFit = 'contain'
parent.appendChild(pixiApp.value.view)
model.value = await Live2DModel.from(props.model, { motionPreload: MotionPreloadStrategy.ALL })
pixiApp.value.stage.addChild(model.value as any)
model.value.x = containerElementBounding.width.value / 2
model.value.y = Math.max(600, containerParentElementBounding.height.value)
model.value.x = canvasWidth.value / 2
model.value.y = canvasHeight.value
model.value.rotation = Math.PI
model.value.skew.x = Math.PI
model.value.scale.set(0.3, 0.3)
@@ -69,18 +97,18 @@ async function setMotion(motionName: string) {
await model.value!.motion(motionName, undefined, MotionPriority.FORCE)
}
watch([width, height], () => {
watch([width, height, canvasWidth, canvasHeight], () => {
if (pixiApp.value)
pixiApp.value.renderer.resize((width.value - 16) / 2, 550)
pixiApp.value.renderer.resize(canvasWidth.value, canvasHeight.value)
if (pixiAppCanvas.value) {
pixiAppCanvas.value.width = (width.value - 16) / 2
pixiAppCanvas.value.height = Math.max(600, containerParentElementBounding.height.value)
pixiAppCanvas.value.width = canvasWidth.value
pixiAppCanvas.value.height = canvasHeight.value
}
if (model.value) {
model.value.x = (width.value - 16) / 4
model.value.y = Math.max(600, containerParentElementBounding.height.value)
model.value.x = canvasWidth.value / 2
model.value.y = canvasHeight.value
}
})
@@ -105,5 +133,5 @@ defineExpose({
</script>
<template>
<div ref="containerRef" h-full w-full />
<div ref="containerRef" />
</template>
+46 -14
View File
@@ -273,15 +273,25 @@ onUnmounted(() => {
<template>
<div max-h="[100vh]" h-full p="2" flex="~ col">
<Settings />
<div flex="~ row 1" w-full items-end space-x-2>
<div w-full min-h="100 sm:100">
<Live2DViewer ref="live2DViewerRef" :mouth-open-size="mouthOpenSize" model="/assets/live2d/models/hiyori_pro_zh/runtime/hiyori_pro_t11.model3.json" />
<div flex="~ row 1" relative w-full items-end gap-2>
<div w="50%" min-w="50% <lg:full" min-h="100 sm:100" flex-1>
<Live2DViewer
ref="live2DViewerRef"
:mouth-open-size="mouthOpenSize"
model="/assets/live2d/models/hiyori_pro_zh/runtime/hiyori_pro_t11.model3.json"
/>
</div>
<div my="2" w-full space-y-2 max-h="[calc(100vh-117px)]">
<div
class="relative <lg:(absolute bottom-0 from-zinc-800/80 to-zinc-800/0 bg-gradient-to-t p-2)"
px="<sm:2" py="<sm:2" rounded="<sm:lg"
w="50% <lg:full" flex="~ col 1" gap-2 max-h="[calc(100vh-117px)]"
>
<div v-for="(message, index) in messages" :key="index">
<div v-if="message.role === 'assistant'" flex mr="12">
<div
mr-2 h-10 min-h-10 min-w-10 w-10 overflow-hidden rounded-full
mr-2 h-10
min-h-10 min-w-10 w-10
overflow-hidden rounded-full
border="solid 3"
transition="all ease-in-out" duration-100
:style="{
@@ -290,22 +300,44 @@ onUnmounted(() => {
>
<img :src="Avatar">
</div>
<div flex="~ col" bg="pink-50/50 dark:pink-900/50" p="2" border="2 solid pink/10" rounded-lg>
<div
flex="~ col"
bg="pink-50 dark:pink-900"
p="2"
border="2 solid pink dark:pink-700"
rounded-lg
h="unset <sm:fit"
>
<div>
<span font-semibold>Neuro</span>
<span font-semibold class="inline <sm:hidden">Neuro</span>
</div>
<div v-html="process(message.content as string)" />
<div v-if="message.content" text="base <sm:xs" v-html="process(message.content as string)" />
<div v-else i-eos-icons:three-dots-loading />
</div>
</div>
<div v-else-if="message.role === 'user'" flex="~ row-reverse" ml="12">
<div border="purple solid 3" ml="2" h-10 min-h-10 min-w-10 w-10 overflow-hidden rounded-full>
<div
class="block <sm:hidden"
border="purple solid 3"
ml="2"
h-10 min-h-10 min-w-10 w-10
overflow-hidden rounded-full
>
<div i-carbon:user-avatar-filled text="purple" h-full w-full p="0" m="0" />
</div>
<div flex="~ col" bg="purple-50/50 dark:purple-900/50" p="2" border="2 solid pink/10" rounded-lg>
<div
flex="~ col"
bg="purple-50 dark:purple-900"
p="2"
border="2 solid purple dark:purple-700"
rounded-lg
h="unset <sm:fit"
>
<div>
<span font-semibold>You</span>
<span font-semibold class="inline <sm:hidden">You</span>
</div>
<div v-html="process(message.content as string)" />
<div v-if="message.content" text="base <sm:xs" v-html="process(message.content as string)" />
<div v-else />
</div>
</div>
</div>
@@ -329,10 +361,10 @@ onUnmounted(() => {
{{ 'name' in m ? `${m.name} (${m.id})` : m.id }}
</option>
</select>
<div absolute bottom="5" left="50%" translate-x="-50%">
<div fixed bottom="5" left="50%" translate-x="-50%">
<button
bg="zinc-100 dark:zinc-700" flex="~ row"
items-center rounded-full px-4 py-2
items-center rounded-full px-4 py-2 border="2 solid zinc-500/50"
transition="all ease-in-out"
@click="listening = !listening"
>
+10
View File
@@ -223,6 +223,9 @@ importers:
'@iconify-json/carbon':
specifier: ^1.2.4
version: 1.2.4
'@iconify-json/eos-icons':
specifier: ^1.2.1
version: 1.2.1
'@intlify/unplugin-vue-i18n':
specifier: ^6.0.0
version: 6.0.0(@vue/compiler-dom@3.5.13)(eslint@9.16.0(jiti@2.4.0))(rollup@2.79.1)(typescript@5.7.2)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))
@@ -1683,6 +1686,9 @@ packages:
'@iconify-json/carbon@1.2.4':
resolution: {integrity: sha512-DhW2jjMVGwV0DLHc0cmDYohdtGxMra8UuwgjHrryPy+rQX4gXhJwCBBVP2h2UG/92AoRCTn7zUJve4WvY5MLYg==}
'@iconify-json/eos-icons@1.2.1':
resolution: {integrity: sha512-nqucN9E670dRHDk5osNNObhxQg/eBI8wvYA+zWaOMsJR0g8VodF9z/cpDvMH7YyUm4elOF9r8cGIj3byFBFLRQ==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -8170,6 +8176,10 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/eos-icons@1.2.1':
dependencies:
'@iconify/types': 2.0.0
'@iconify/types@2.0.0': {}
'@iconify/utils@2.1.33':