feat(stage): custom primary colors (#53)

* feat(stage-ui): allow disable transitions
* fix(stage-web): apply motion map from file
* chore(stage-web): change icon
* feat(stage): custom primary colors
This commit is contained in:
LemonNeko
2025-03-08 16:51:48 +08:00
committed by GitHub
parent 8b824e8ccf
commit 25e06085f3
23 changed files with 402 additions and 215 deletions
@@ -36,7 +36,7 @@ onTokenLiteral(async () => {
<div flex-1 /> <!-- spacer -->
<div
ref="chatHistoryRef" v-auto-animate max-h="30vh" flex="~ col"
scrollbar="~ w-2 track-color-transparent thumb-color-pink-300 rounded" h-full w-full overflow-scroll
scrollbar="~ w-2 track-color-transparent thumb-color-primary-300 rounded" h-full w-full overflow-scroll
>
<div flex-1 /> <!-- spacer -->
<div v-for="(message, index) in messages" :key="index" mb-2>
@@ -61,14 +61,14 @@ onTokenLiteral(async () => {
</div>
<div v-if="message.role === 'assistant'" flex mr="12">
<div
flex="~ col" border="4 solid pink-200" shadow="md pink-200/50" min-w-20 rounded-lg px-2 py-1 h="fit"
bg="pink-100"
flex="~ col" border="4 solid primary-200" shadow="md primary-200/50" min-w-20 rounded-lg px-2 py-1 h="fit"
bg="primary-100"
>
<div>
<span text-xs text="pink-400/90" font-semibold class="inline hidden">Airi</span>
<span text-xs text="primary-400/90" font-semibold class="inline hidden">Airi</span>
</div>
<div
v-if="message.content" class="markdown-content" text="xs pink-400"
v-if="message.content" class="markdown-content" text="xs primary-400"
v-html="process(message.content as string)"
/>
<div v-else i-eos-icons:three-dots-loading />
@@ -104,9 +104,9 @@ onMounted(() => {
<BasicTextarea
v-model="messageInput"
:placeholder="t('stage.message')"
border="solid 2 pink-100"
text="pink-400 hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600"
bg="pink-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
border="solid 2 primary-100"
text="primary-400 hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600"
bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
w-full resize-none overflow-y-scroll rounded-l-xl p-2 font-medium outline-none
transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out"
@submit="handleSend"
@@ -114,9 +114,9 @@ onMounted(() => {
</div>
<div
class="px-4 py-2.5"
border="solid 2 pink-100 "
text="lg pink-400 hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600"
bg="pink-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
border="solid 2 primary-100 "
text="lg primary-400 hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600"
bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
flex items-center justify-center rounded-r-xl
@click="openSettings"
>
@@ -64,7 +64,7 @@ const modeIndicatorClass = computed(() => {
>
<div class="absolute h-32 w-full flex items-center justify-center b-2 b-pink bg-white">
<div class="wall absolute top-0 h-8" />
<div class="absolute left-0 top-0 h-full w-full flex animate-flash animate-duration-5s animate-count-infinite items-center justify-center text-1.5rem text-pink-300 font-bold">
<div class="text-primary-300 absolute left-0 top-0 h-full w-full flex animate-flash animate-duration-5s animate-count-infinite items-center justify-center text-1.5rem font-bold">
DRAG HERE TO MOVE
</div>
<div class="wall absolute bottom-0 h-8" />
@@ -113,7 +113,7 @@ const modeIndicatorClass = computed(() => {
}
.wall {
@apply text-pink-200;
@apply text-primary-200;
--wall-width: 8px;
animation: wall-move 1s linear infinite;
@@ -153,9 +153,9 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
<div>
<div
grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg
bg="[#fff6fc]" px-2 py-1 text="pink-400"
bg="[#fff6fc]" px-2 py-1 text="primary-400"
>
<div text="xs pink-500">
<div text="xs primary-500">
<span>{{ t('settings.openai-base-url.label') }}</span>
</div>
<div flex="~ row" w-full text="xs">
@@ -166,7 +166,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
h-6 w-full rounded-md bg-transparent px-2 py-1 text-right font-mono outline-none
>
</div>
<div text="xs pink-500">
<div text="xs primary-500">
<span>{{ t('settings.openai-api-key.label') }}</span>
</div>
<div flex="~ row" w-full text="xs">
@@ -177,7 +177,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
h-6 w-full rounded-md bg-transparent px-2 py-1 text-right font-mono outline-none
>
</div>
<div text="xs pink-500">
<div text="xs primary-500">
<span>{{ t('settings.elevenlabs-api-key.label') }}</span>
</div>
<div flex="~ row" w-full text="xs">
@@ -188,7 +188,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
h-6 w-full rounded-md bg-transparent px-2 py-1 text-right font-mono outline-none
>
</div>
<div text="xs pink-500">
<div text="xs primary-500">
<span>{{ t('settings.language.title') }}</span>
</div>
<div flex="~ row" w-full text="xs">
@@ -204,7 +204,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
</option>
</select>
</div>
<div text="xs pink-500">
<div text="xs primary-500">
<span>{{ t('settings.models') }}</span>
</div>
<div flex="~ row" w-full text="xs">
@@ -223,7 +223,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
</option>
</select>
</div>
<div text="xs pink-500">
<div text="xs primary-500">
<span>{{ t('settings.voices') }}</span>
</div>
<div flex="~ row" w-full text="xs">
@@ -257,9 +257,9 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
<div>
<div
grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg
bg="[#fff6fc]" px-2 py-1 text="pink-400"
bg="[#fff6fc]" px-2 py-1 text="primary-400"
>
<div text="xs pink-500">
<div text="xs primary-500">
<span>{{ t('settings.viewer') }}</span>
</div>
<select
@@ -281,16 +281,16 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
<div pb-2>
<div
grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg
bg="[#fff6fc]" p-2 text="pink-400"
bg="[#fff6fc]" p-2 text="primary-400"
>
<template v-for="shortcut in shortcuts" :key="shortcut.type">
<span text="xs pink-500">
<span text="xs primary-500">
{{ t(shortcut.name) }}
</span>
<div
class="shortcut-item flex items-center justify-end gap-x-2 px-2 py-0.5"
:class="{ recording: recordingFor === shortcut.type }"
text="xs pink-500"
text="xs primary-500"
cursor-pointer
@click="startRecording(shortcut)"
>
@@ -300,7 +300,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
<div v-else class="pointer-events-none">
{{ shortcut.shortcut }}
</div>
<div v-if="isConflict(shortcut)" text="xs pink-500" i-solar:danger-square-bold w-4 />
<div v-if="isConflict(shortcut)" text="xs primary-500" i-solar:danger-square-bold w-4 />
</div>
</template>
</div>
@@ -311,14 +311,14 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
<div pb-2>
<div
grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg
bg="[#fff6fc]" p-2 text="pink-400" @click="handleQuit"
bg="[#fff6fc]" p-2 text="primary-400" @click="handleQuit"
>
<div text="xs pink-500">
<div text="xs primary-500">
<span>
{{ t('settings.quit') }}
</span>
</div>
<div text="sm pink-500" text-right>
<div text="sm primary-500" text-right>
<div i-solar:exit-bold-duotone ml-auto />
</div>
</div>
+10 -1
View File
@@ -3,7 +3,7 @@ import { useSettings } from '@proj-airi/stage-ui/stores/settings'
import { StageTransitionGroup } from '@proj-airi/ui-transitions'
import { useDark } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { watch } from 'vue'
import { onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterView } from 'vue-router'
@@ -14,6 +14,14 @@ const isDark = useDark()
watch(settings.language, () => {
i18n.locale.value = settings.language.value
})
watch(settings.themeColorsHue, () => {
document.documentElement.style.setProperty('--theme-colors-hue', settings.themeColorsHue.value.toString())
})
onMounted(() => {
document.documentElement.style.setProperty('--theme-colors-hue', settings.themeColorsHue.value.toString())
})
</script>
<template>
@@ -23,6 +31,7 @@ watch(settings.language, () => {
tertiary-color="#121212"
:colors="!isDark ? ['#FF57C8', '#946BFF', '#64BCFF', '#FFFFFF'] : ['#FF57C8', '#946BFF', '#64BCFF', '#121212']"
:z-index="100"
:disable-transitions="settings.disableTransitions.value"
>
<RouterView />
</StageTransitionGroup>
@@ -11,11 +11,12 @@ https://www.magicpattern.design/tools/css-backgrounds
*/
.cross-background-container {
background-color: #ffffff;
--cross-color: oklch(0.95 0.06 var(--theme-colors-hue));
background:
radial-gradient(circle, transparent 20%, #ffffff 20%, #ffffff 80%, transparent 80%, transparent),
radial-gradient(circle, transparent 20%, #ffffff 20%, #ffffff 80%, transparent 80%, transparent) 25px 25px,
linear-gradient(#f4ebf1 2px, transparent 2px) 0 -1px,
linear-gradient(90deg, #f4ebf1 2px, #ffffff 2px) -1px 0;
linear-gradient(var(--cross-color) 2px, transparent 2px) 0 -1px,
linear-gradient(90deg, var(--cross-color) 2px, #ffffff 2px) -1px 0;
background-size:
50px 50px,
50px 50px,
@@ -17,7 +17,7 @@ const props = withDefaults(defineProps<WaveProps>(), {
height: 40,
amplitude: 14,
waveLength: 250,
fillColor: '#f8e8f2',
fillColor: 'oklch(95% 0.10 var(--theme-colors-hue))',
direction: 'down',
animationSpeed: 50,
})
@@ -120,15 +120,15 @@ onAfterSend(async () => {
<fieldset flex="~ row" w-fit rounded-lg>
<label
:class="[
tab === 'chat' ? 'bg-pink-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]',
tab === 'chat' ? 'text-pink-500 dark:text-pink-500' : '',
tab === 'chat' ? 'bg-primary-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]',
tab === 'chat' ? 'text-primary-500 dark:text-primary-500' : '',
]"
flex="~ row"
:checked="tab === 'chat'"
:aria-checked="tab === 'chat'"
border="solid l-2 t-2 b-2 pink-100 dark:[#3c2632]"
bg="hover:pink-100 dark:hover:[#3c2632]"
text="pink-300 hover:pink-500 dark:pink-300/50 dark:hover:pink-500"
border="solid l-2 t-2 b-2 primary-100 dark:[#3c2632]"
bg="hover:primary-100 dark:hover:[#3c2632]"
text="primary-300 hover:primary-500 dark:primary-300/50 dark:hover:primary-500"
transition="all duration-250 ease-in-out"
cursor-pointer items-center gap-1 rounded-l-lg px-2
>
@@ -140,15 +140,15 @@ onAfterSend(async () => {
</label>
<label
:class="[
tab === 'custom' ? 'bg-pink-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]',
tab === 'custom' ? 'text-pink-500 dark:text-pink-500' : '',
tab === 'custom' ? 'bg-primary-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]',
tab === 'custom' ? 'text-primary-500 dark:text-primary-500' : '',
]"
flex="~ row"
:checked="tab === 'custom'"
:aria-checked="tab === 'custom'"
border="solid t-2 b-2 pink-100 dark:[#3c2632]"
bg="hover:pink-100 dark:hover:[#3c2632]"
text="pink-300 hover:pink-500 dark:pink-300/50 dark:hover:pink-500"
border="solid t-2 b-2 primary-100 dark:[#3c2632]"
bg="hover:primary-100 dark:hover:[#3c2632]"
text="primary-300 hover:primary-500 dark:primary-300/50 dark:hover:primary-500"
transition="all duration-250 ease-in-out"
cursor-pointer items-center gap-1 px-2
>
@@ -160,15 +160,15 @@ onAfterSend(async () => {
</label>
<label
:class="[
tab === 'clothes' ? 'bg-pink-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]',
tab === 'clothes' ? 'text-pink-500 dark:text-pink-500' : '',
tab === 'clothes' ? 'bg-primary-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]',
tab === 'clothes' ? 'text-primary-500 dark:text-primary-500' : '',
]"
flex="~ row"
:checked="tab === 'clothes'"
:aria-checked="tab === 'clothes'"
border="solid r-2 t-2 b-2 pink-100 dark:[#3c2632]"
bg="hover:pink-100 dark:hover:[#3c2632]"
text="pink-300 hover:pink-500 dark:pink-300/50 dark:hover:pink-500"
border="solid r-2 t-2 b-2 primary-100 dark:[#3c2632]"
bg="hover:primary-100 dark:hover:[#3c2632]"
text="primary-300 hover:primary-500 dark:primary-300/50 dark:hover:primary-500"
transition="all duration-250 ease-in-out"
cursor-pointer items-center gap-1 rounded-r-lg px-2
>
@@ -182,7 +182,7 @@ onAfterSend(async () => {
<div h-full max-h="[85vh]" w-full px="12 <md:0" py="4">
<div
flex="~ col"
border="solid 4 pink-100 dark:pink-400/20"
border="solid 4 primary-100 dark:primary-400/20"
h-full w-full overflow-scroll rounded-xl
bg="white dark:[#0f060c]"
>
@@ -191,8 +191,8 @@ onAfterSend(async () => {
<BasicTextarea
v-model="messageInput"
:placeholder="t('stage.message')"
text="pink-300 hover:pink-500 dark:pink-300/50 dark:hover:pink-500 placeholder:pink-300 placeholder:hover:pink-500 placeholder:dark:pink-300/50 placeholder:dark:hover:pink-500"
bg="pink-100 dark:pink-400/20"
text="primary-300 hover:primary-500 dark:primary-300/50 dark:hover:primary-500 placeholder:primary-300 placeholder:hover:primary-500 placeholder:dark:primary-300/50 placeholder:dark:hover:primary-500"
bg="primary-100 dark:primary-400/20"
min-h="[100px]" w-full
rounded-t-xl p-4 font-medium
outline-none transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out"
@@ -100,9 +100,9 @@ onMounted(() => {
<BasicTextarea
v-model="messageInput"
:placeholder="t('stage.message')"
border="solid 2 pink-100 dark:pink-400/20"
text="pink-400 hover:pink-600 dark:[#905073] dark:hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600 placeholder:dark:[#905073] placeholder:dark:hover:pink-600"
bg="pink-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
border="solid 2 primary-100 dark:primary-400/20"
text="primary-400 hover:primary-600 dark:[#905073] dark:hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600 placeholder:dark:[#905073] placeholder:dark:hover:primary-600"
bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
w-full resize-none overflow-y-scroll rounded-l-xl p-2 font-medium outline-none
transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out"
@submit="handleSend"
@@ -111,9 +111,9 @@ onMounted(() => {
<DrawerRoot should-scale-background @update:open="handleSettingsOpen">
<DrawerTrigger
class="px-4 py-2.5"
border="solid 2 pink-100 dark:pink-400/20"
text="lg pink-400 hover:pink-600 dark:[#905073] dark:hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600 placeholder:dark:[#905073] placeholder:dark:hover:pink-600"
bg="pink-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]" rounded-r-xl
border="solid 2 primary-100 dark:primary-400/20"
text="lg primary-400 hover:primary-600 dark:[#905073] dark:hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600 placeholder:dark:[#905073] placeholder:dark:hover:primary-600"
bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]" rounded-r-xl
>
<div i-solar:settings-bold-duotone />
</DrawerTrigger>
@@ -11,9 +11,9 @@ defineProps<{
<template>
<RouterLink
flex="~ row" bg="neutral-50 dark:neutral-800"
border="neutral-100 dark:neutral-700 hover:pink-300 dark:hover:pink-300/40 solid 2"
border="neutral-100 dark:neutral-700 hover:primary-300 dark:hover:primary-300/40 solid 2"
drop-shadow="none hover:[0px_4px_4px_rgba(220,220,220,0.4)] active:[0px_0px_0px_rgba(220,220,220,0.25)] dark:hover:none"
class="after:mask-[linear-gradient(165deg,white_30%,transparent_50%)] after:bg-dotted-[neutral-200] after:hover:bg-dotted-[pink-300/50] dark:after:bg-dotted-[neutral-700/80] dark:after:hover:bg-dotted-[pink-200/20] after:absolute after:left-0 after:top-0 after:z--1 after:h-full after:w-full after:bg-[size:10px_10px] after:content-empty [&_.settings-section-icon]:hover:scale-120 [&_.settings-section-description]:hover:text-pink-400/80 [&_.settings-section-icon]:hover:text-pink-200 [&_.settings-section-title]:hover:text-pink-500 dark:[&_.settings-section-icon]:hover:text-pink-200/40 dark:[&_.settings-section-title]:hover:text-pink-400"
class="after:mask-[linear-gradient(165deg,white_30%,transparent_50%)] after:bg-dotted-[neutral-200] after:hover:bg-dotted-[primary-300/50] dark:after:bg-dotted-[neutral-700/80] [&_.settings-section-description]:hover:text-primary-400/80 [&_.settings-section-icon]:hover:text-primary-200 [&_.settings-section-title]:hover:text-primary-500 dark:after:hover:bg-dotted-[primary-200/20] dark:[&_.settings-section-icon]:hover:text-primary-200/40 dark:[&_.settings-section-title]:hover:text-primary-400 after:absolute after:left-0 after:top-0 after:z--1 after:h-full after:w-full after:bg-[size:10px_10px] after:content-empty [&_.settings-section-icon]:hover:scale-120"
transition="all ease-in-out duration-200" relative w-full items-center overflow-hidden rounded-lg p-5 text-left
:to="to"
>
@@ -58,11 +58,11 @@ onTokenLiteral(async () => {
</div>
<div v-if="message.role === 'assistant'" flex mr="12">
<div
flex="~ col" border="4 solid pink-200/50 dark:pink-500/50" shadow="md pink-200/50 dark:none" min-w-20
rounded-lg px-2 py-1 h="unset <sm:fit" bg="<md:pink-500/25"
flex="~ col" border="4 solid primary-200/50 dark:primary-500/50" shadow="md primary-200/50 dark:none" min-w-20
rounded-lg px-2 py-1 h="unset <sm:fit" bg="<md:primary-500/25"
>
<div>
<span text-xs text="pink-400/90 dark:pink-600/90" font-semibold class="inline <sm:hidden">{{
<span text-xs text="primary-400/90 dark:primary-600/90" font-semibold class="inline <sm:hidden">{{
$t('stage.chat.message.character-name.airi') }}</span>
</div>
<div v-if="sending" i-eos-icons:three-dots-loading />
@@ -31,8 +31,15 @@ watch(() => settings.loadingLive2dModel, (value) => {
return
}
if (settings.live2dLoadSource !== 'file') {
return
}
settings.availableLive2dMotions.forEach((motion) => {
if (!settings.live2dMotionMap[motion.fileName]) {
if (motion.motionName in Emotion) {
settings.live2dMotionMap[motion.fileName] = motion.motionName
}
else {
settings.live2dMotionMap[motion.fileName] = EmotionNeutralMotionName
}
})
@@ -88,7 +95,8 @@ const exportObjectUrl = useObjectUrl(settings.live2dModelFile)
</script>
<template>
<Collapsable w-full>
<div>
<Collapsable w-full :default="true">
<template #trigger="slotProps">
<button
bg="zinc-100 dark:zinc-800"
@@ -164,7 +172,7 @@ const exportObjectUrl = useObjectUrl(settings.live2dModelFile)
</div>
</div>
</Collapsable>
<Collapsable mt-4 w-full>
<Collapsable mt-4 w-full :default="true">
<template #trigger="slotProps">
<button
bg="zinc-100 dark:zinc-800"
@@ -245,4 +253,5 @@ const exportObjectUrl = useObjectUrl(settings.live2dModelFile)
</div>
</div>
</Collapsable>
</div>
</template>
@@ -40,14 +40,14 @@ onTokenLiteral(async () => {
<div v-if="message.role === 'assistant'" flex mr="12">
<div
flex="~ col"
border="4 solid pink-200/50 dark:pink-500/50"
shadow="md pink-200/50 dark:none"
border="4 solid primary-200/50 dark:primary-500/50"
shadow="md primary-200/50 dark:none"
min-w-20 rounded-lg px-2 py-1
h="unset <sm:fit"
bg="<md:pink-500/25"
bg="<md:primary-500/25"
>
<div>
<span text-xs text="pink-400/90 dark:pink-600/90" font-semibold class="inline <sm:hidden">{{ $t('stage.chat.message.character-name.airi') }}</span>
<span text-xs text="primary-400/90 dark:primary-600/90" font-semibold class="inline <sm:hidden">{{ $t('stage.chat.message.character-name.airi') }}</span>
</div>
<div v-if="message.content" class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
<div v-else i-eos-icons:three-dots-loading />
+1 -1
View File
@@ -20,7 +20,7 @@ function handleSettingsOpen(open: boolean) {
<template>
<Cross>
<AnimatedBackground :fill-color="dark ? '#563544' : '#f8e8f2'">
<AnimatedBackground :fill-color="dark ? 'oklch(95% 0.20 var(--theme-colors-hue))' : 'oklch(99% 0.10 var(--theme-colors-hue))'">
<div relative flex="~ col" z-2 h-100vh w-100vw of-hidden>
<!-- header -->
<div>
+37 -2
View File
@@ -9,12 +9,12 @@ import IconItem from '../../components/Menu/IconItem.vue'
const router = useRouter()
const { t } = useI18n()
const settings = storeToRefs(useSettings())
const { language, disableTransitions } = storeToRefs(useSettings())
const dark = useDark()
function handleLanguageChange(event: Event) {
const target = event.target as HTMLSelectElement
settings.language.value = target.value
language.value = target.value
}
</script>
@@ -104,6 +104,41 @@ function handleLanguageChange(event: Event) {
</div>
</div>
</label>
<!-- Developer Settings -->
<label
bg="neutral-100 dark:neutral-800"
hover="bg-neutral-200 dark:bg-neutral-700"
transition="all ease-in-out duration-250"
w-full flex cursor-pointer rounded-lg px-4 py-3
>
<input
v-model="disableTransitions"
text="neutral-800 dark:neutral-100"
:checked="disableTransitions"
:aria-checked="disableTransitions"
type="checkbox"
hidden appearance-none outline-none
>
<div flex="~ row" w-full items-center gap-1.5>
<div text="sm" w-full flex-1>
<span>Disable Transitions (for debugging)</span>
</div>
<div select-none>
<Transition name="slide-away" mode="out-in">
<div
v-if="disableTransitions"
i-solar:people-nearby-bold-duotone
transition="all ease-in-out duration-250"
/>
<div
v-else
i-solar:running-2-line-duotone
transition="all ease-in-out duration-250"
/>
</Transition>
</div>
</div>
</label>
</div>
<div fixed bottom-0 right-0 text="neutral-100/50 dark:neutral-500/20">
<div text="40" i-lucide:cog translate-x-10 translate-y-10 />
@@ -1,6 +1,9 @@
<script setup lang="ts">
import { WidgetStage } from '@proj-airi/stage-ui/components'
import { useRouter } from 'vue-router'
import Live2DSettings from '../../../components/Widgets/Live2DSettings.vue'
const router = useRouter()
</script>
@@ -18,6 +21,11 @@ const router = useRouter()
</div>
</h1>
</div>
<div flex>
<WidgetStage w="50%" h="80vh" />
<Live2DSettings w="50%" h="80vh" />
</div>
<div fixed bottom-0 right-0 z--1 text="neutral-100/80 dark:neutral-500/20">
<div text="40" i-lucide:person-standing translate-x-10 translate-y-10 />
</div>
@@ -111,9 +111,9 @@ const modulesList = computed<Module[]>(() => [
<div
v-for="module in modulesList" :key="module.id"
bg="neutral-200/50 dark:neutral-700"
border="neutral-100 dark:neutral-700 hover:pink-300 dark:hover:pink-300/40 solid 2"
border="neutral-100 dark:neutral-700 hover:primary-300 dark:hover:primary-300/40 solid 2"
drop-shadow="none hover:[0px_4px_4px_rgba(220,220,220,0.4)] active:[0px_0px_0px_rgba(220,220,220,0.25)] dark:hover:none"
class="[&_.settings-section-icon]:hover:scale-120 [&_.settings-section-description]:hover:text-pink-400/80 [&_.settings-section-icon]:hover:text-pink-200 [&_.settings-section-icon]:hover:grayscale-0 dark:[&_.settings-section-icon]:hover:text-pink-200/40 dark:[&_.settings-section-title]:hover:text-pink-400"
class="[&_.settings-section-description]:hover:text-primary-400/80 [&_.settings-section-icon]:hover:text-primary-200 dark:[&_.settings-section-icon]:hover:text-primary-200/40 dark:[&_.settings-section-title]:hover:text-primary-400 [&_.settings-section-icon]:hover:scale-120 [&_.settings-section-icon]:hover:grayscale-0"
w-full of-hidden rounded-xl
flex="~ col 1"
>
@@ -1,7 +1,10 @@
<script setup lang="ts">
import { Collapsable } from '@proj-airi/stage-ui/components'
import { useSettings } from '@proj-airi/stage-ui/stores'
import { useRouter } from 'vue-router'
const router = useRouter()
const settings = useSettings()
</script>
<template>
@@ -18,11 +21,98 @@ const router = useRouter()
</div>
</h1>
</div>
<Collapsable mt-4 w-full :default="true">
<template #trigger="slotProps">
<button
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
w-full flex items-center gap-1.5 rounded-lg px-4 py-3 outline-none
class="[&_.provider-icon]:grayscale-100 [&_.provider-icon]:hover:grayscale-0"
@click="slotProps.setVisible(!slotProps.visible)"
>
<div flex="~ row 1" items-center gap-1.5>
<div
i-solar:pallete-2-bold-duotone class="provider-icon size-6"
transition="filter duration-250 ease-in-out"
/>
<div>
Colors
</div>
</div>
<div transform transition="transform duration-250" :class="{ 'rotate-180': slotProps.visible }">
<div i-solar:alt-arrow-down-bold-duotone />
</div>
</button>
</template>
<div p-4>
<div class="flex items-center justify-between">
<div class="flex items-center gap-1 text-sm font-medium">
Primary color
</div>
<input v-model="settings.themeColorsHue" type="range" min="0" max="360" step="1">
</div>
<div mt-4 h-10 w-full flex overflow-hidden rounded-lg>
<div bg="primary-50" class="primary-color-bar">
50
</div>
<div bg="primary-100" class="primary-color-bar">
100
</div>
<div bg="primary-200" class="primary-color-bar">
200
</div>
<div bg="primary-300" class="primary-color-bar">
300
</div>
<div bg="primary-400" class="primary-color-bar">
400
</div>
<div bg="primary-500" class="primary-color-bar">
500
</div>
<div bg="primary-600" class="primary-color-bar">
600
</div>
<div bg="primary-700" class="primary-color-bar">
700
</div>
<div bg="primary-800" class="primary-color-bar" text-white>
800
</div>
<div bg="primary-900" class="primary-color-bar" text-white>
900
</div>
<div bg="primary-950" class="primary-color-bar" text-white>
950
</div>
<div bg="primary-960" class="primary-color-bar" text-white>
960
</div>
<div bg="primary-970" class="primary-color-bar" text-white>
970
</div>
<div bg="primary-980" class="primary-color-bar" text-white>
980
</div>
<div bg="primary-990" class="primary-color-bar" text-white>
990
</div>
</div>
</div>
</Collapsable>
<div fixed bottom-0 right-0 z--1 text="neutral-100/80 dark:neutral-500/20">
<div text="40" i-lucide:paintbrush translate-x-10 translate-y-10 />
</div>
</template>
<style>
.primary-color-bar {
@apply w-full h-full flex-1 flex items-center justify-center;
}
</style>
<route lang="yaml">
meta:
stageTransition:
+1
View File
@@ -10,4 +10,5 @@
--airi-theme-primary-800: #a70a3e;
--airi-theme-primary-900: #8e0d3b;
--airi-theme-primary-950: #50011b;
--theme-colors-hue: 354.31;
}
+22
View File
@@ -69,4 +69,26 @@ export default defineConfig({
}
}],
],
theme: {
colors: {
primary: {
DEFAULT: 'oklch(62% 0.20 var(--theme-colors-hue))',
50: 'color-mix(in srgb, oklch(95% 0.10 var(--theme-colors-hue)) 30%, oklch(100% 0 360))',
100: 'color-mix(in srgb, oklch(95% 0.10 var(--theme-colors-hue)) 80%, oklch(100% 0 360))',
200: 'oklch(90% 0.12 var(--theme-colors-hue))',
300: 'oklch(85% 0.20 var(--theme-colors-hue))',
400: 'oklch(74% 0.20 var(--theme-colors-hue))',
500: 'oklch(62% 0.20 var(--theme-colors-hue))',
600: 'oklch(54% 0.23 var(--theme-colors-hue))',
700: 'oklch(49% 0.21 var(--theme-colors-hue))',
800: 'oklch(42% 0.17 var(--theme-colors-hue))',
900: 'oklch(37% 0.14 var(--theme-colors-hue))',
950: 'oklch(29% 0.09 var(--theme-colors-hue))',
960: 'oklch(23% 0.09 var(--theme-colors-hue))',
970: 'oklch(17% 0.09 var(--theme-colors-hue))',
980: 'oklch(11% 0.09 var(--theme-colors-hue))',
990: 'oklch(6% 0.09 var(--theme-colors-hue))',
},
},
},
})
@@ -252,11 +252,11 @@ onMounted(async () => {
bottom="20%"
z="20"
rounded-2xl
text="pink-600"
text="primary-600"
px-2 py-2
transform="translate-x--1/2"
>
<div bg="pink-50" rounded-xl px-10 py-6>
<div bg="primary-50" rounded-xl px-10 py-6>
<div class="markdown-content" v-html="process(streamingMessage.content as string)" />
</div>
</div>
+6
View File
@@ -33,6 +33,10 @@ export const useSettings = defineStore('settings', () => {
const availableLive2dMotions = ref<{ motionName: string, motionIndex: number, fileName: string }[]>([])
const live2dMotionMap = useLocalStorage<Record<string, string>>('settings/live2d/motion-map', {})
const disableTransitions = useLocalStorage('settings/disable-transitions', false)
const themeColorsHue = useLocalStorage('settings/theme/colors/hue', 354.31)
watch(isAudioInputOn, (value) => {
if (value === 'false') {
selectedAudioDevice.value = undefined
@@ -67,8 +71,10 @@ export const useSettings = defineStore('settings', () => {
availableLive2dMotions,
live2dMotionMap,
loadingLive2dModel,
disableTransitions,
language,
stageView,
themeColorsHue,
isAudioInputOn,
selectedAudioDevice,
selectedAudioDeviceId,
@@ -16,6 +16,7 @@ const props = defineProps<{
tertiaryColor?: string
colors?: string[]
zIndex?: number
disableTransitions?: boolean
}>()
interface StageTransitionCommonParams {
@@ -268,6 +269,11 @@ router.beforeEach((to, _from, next) => {
stageTransition.zIndex = props.zIndex
}
if (props.disableTransitions) {
next()
return
}
triggerTransition(stageTransition, next)
})
</script>