refactor(docs): home layout component (#1404)
--------- Signed-off-by: 秋雨落 <i@rain.cx> Co-authored-by: Neko-233 <1260907335@qq.com> Co-authored-by: Neko <neko@ayaka.moe>
This commit is contained in:
@@ -3,7 +3,8 @@ import type { AnimatableObject } from 'animejs'
|
||||
|
||||
import { useLocalStorage, useWindowSize } from '@vueuse/core'
|
||||
import { createAnimatable } from 'animejs'
|
||||
import { onMounted, shallowRef, useTemplateRef, watch, watchEffect } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
import { computed, onMounted, shallowRef, useTemplateRef, watch, watchEffect } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import homeBackgroundChristmas20251224 from '../assets/home-cover-2025-12-24-bg.avif'
|
||||
@@ -12,6 +13,7 @@ import homeBackgroundChristmas20251224 from '../assets/home-cover-2025-12-24-bg.
|
||||
// cached or erased in dev/prod, causing the mask pattern to not show up.
|
||||
import homeBackgroundPatternGhost from '../assets/home-patterns-ghost.svg?no-inline'
|
||||
import homeBackgroundPatternLollipop from '../assets/home-patterns-lollipop.svg?no-inline'
|
||||
import HomeButtonSet from './HomeButtonSet.vue'
|
||||
import ParallaxCover from './ParallaxCover.vue'
|
||||
import ParallaxCoverChristmas20251224 from './ParallaxCoverChristmas20251224.vue'
|
||||
import ParallaxCoverHalloween20251029 from './ParallaxCoverHalloween20251029.vue'
|
||||
@@ -89,6 +91,13 @@ watchEffect((onCleanup) => {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isShowHalloween = isBetweenHalloweenAndHalfOfNovember(new Date())
|
||||
const isShowChristmas = isBetweenChristmasAndHalfOfJanuary(new Date())
|
||||
|
||||
const { theme, frontmatter } = useData()
|
||||
|
||||
const buttons = computed(() => theme.value.homepage?.buttons || [])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -100,7 +109,7 @@ watchEffect((onCleanup) => {
|
||||
<div class="relative w-full flex flex-col items-center justify-center text-center font-extrabold font-sans-rounded" text="4xl md:5xl">
|
||||
<ClientOnly>
|
||||
<div
|
||||
v-if="isBetweenHalloweenAndHalfOfNovember(new Date())"
|
||||
v-if="isShowHalloween"
|
||||
:class="[
|
||||
'w-fit',
|
||||
'flex items-center gap-2',
|
||||
@@ -113,7 +122,7 @@ watchEffect((onCleanup) => {
|
||||
<div i-twemoji:jack-o-lantern />Happy Halloween!<div i-twemoji:jack-o-lantern />
|
||||
</div>
|
||||
<div
|
||||
v-if="isBetweenChristmasAndHalfOfJanuary(new Date())"
|
||||
v-if="isShowChristmas"
|
||||
:class="[
|
||||
'w-fit',
|
||||
'flex items-center gap-2',
|
||||
@@ -125,55 +134,22 @@ watchEffect((onCleanup) => {
|
||||
>
|
||||
<div i-twemoji:christmas-tree />Merry Christmas!<div i-twemoji:christmas-tree />
|
||||
</div>
|
||||
<div :class="[isBetweenHalloweenAndHalfOfNovember(new Date()) ? 'font-sans-serif-halloween' : '']">
|
||||
Project AIRI
|
||||
<div
|
||||
v-if="frontmatter.title"
|
||||
:class="[isShowHalloween ? 'font-sans-serif-halloween' : '']"
|
||||
>
|
||||
{{ frontmatter.title }}
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
<div
|
||||
v-if="frontmatter.slogan"
|
||||
class="relative max-w-prose text-center text-slate-900 dark:text-white"
|
||||
:class="[isBetweenHalloweenAndHalfOfNovember(new Date()) ? 'font-sans-serif-halloween-secondary' : '']"
|
||||
:class="[isShowHalloween ? 'font-sans-serif-halloween-secondary' : '']"
|
||||
>
|
||||
{{ t('docs.theme.home.subtitle') }}
|
||||
</div>
|
||||
<div class="relative z-10 w-full flex justify-center gap-4">
|
||||
<a
|
||||
:class="[
|
||||
'rounded-xl px-3 py-2 lg:px-5 lg:py-3 font-extrabold outline-none backdrop-blur-md active:scale-95 focus:outline-none text-nowrap text-sm md:text-base',
|
||||
'text-slate-700 dark:text-cyan-200',
|
||||
]"
|
||||
bg="primary/20 dark:primary/30 dark:hover:primary/40"
|
||||
transition="colors,transform duration-200 ease-in-out"
|
||||
cursor-pointer
|
||||
@click="() => handleClickTryLive()"
|
||||
>
|
||||
{{ t('docs.theme.home.try-live.title') }}
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/moeru-ai/airi/releases/latest"
|
||||
:class="[
|
||||
'rounded-xl px-3 py-2 lg:px-5 lg:py-3 font-extrabold outline-none backdrop-blur-md active:scale-95 focus:outline-none text-nowrap text-sm md:text-base',
|
||||
'text-slate-700 dark:text-slate-100',
|
||||
]"
|
||||
bg="black/4 dark:white/20 dark:hover:white/30"
|
||||
cursor-pointer
|
||||
transition="colors,transform duration-200 ease-in-out"
|
||||
>
|
||||
{{ t('docs.theme.home.download.title') }}
|
||||
</a>
|
||||
<a
|
||||
href="./docs/overview/"
|
||||
:class="[
|
||||
'rounded-xl px-3 py-2 lg:px-5 lg:py-3 font-extrabold outline-none backdrop-blur-md active:scale-95 focus:outline-none text-nowrap text-sm md:text-base',
|
||||
'text-slate-700 dark:text-slate-100',
|
||||
]"
|
||||
bg="black/4 dark:white/20 dark:hover:white/30"
|
||||
transition="colors,transform duration-200 ease-in-out"
|
||||
cursor-pointer
|
||||
>
|
||||
{{ t('docs.theme.home.get-started.title') }}
|
||||
</a>
|
||||
{{ frontmatter.slogan }}
|
||||
</div>
|
||||
<HomeButtonSet :items="buttons" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -181,10 +157,10 @@ watchEffect((onCleanup) => {
|
||||
<div class="absolute inset-0 overflow-hidden -z-10">
|
||||
<!-- Repeating, slowly scrolling SVG pattern background -->
|
||||
<ClientOnly>
|
||||
<template v-if="isBetweenHalloweenAndHalfOfNovember(new Date())">
|
||||
<template v-if="isShowHalloween">
|
||||
<div class="bg-icon-pattern pointer-events-none absolute inset-0 z-0 opacity-10 dark:opacity-10" :style="{ '--bg-mask-icon-pattern': `url(${homeBackgroundPatternGhost})` }" />
|
||||
</template>
|
||||
<template v-if="isBetweenChristmasAndHalfOfJanuary(new Date())">
|
||||
<template v-if="isShowChristmas">
|
||||
<img :src="homeBackgroundChristmas20251224" class="pointer-events-none absolute inset-0 z-0 h-full w-full object-cover">
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -195,14 +171,14 @@ watchEffect((onCleanup) => {
|
||||
:class="[
|
||||
'absolute bottom-0 left-0 right-0 top-0 z-2',
|
||||
'from-transparent to-white bg-gradient-to-t dark:to-[hsl(207_15%_5%)]',
|
||||
isBetweenChristmasAndHalfOfJanuary(new Date()) ? 'h-[40%]' : 'h-[80%]',
|
||||
isShowChristmas ? 'h-[40%]' : 'h-[80%]',
|
||||
]"
|
||||
style="--un-gradient-shape: to top in srgb;"
|
||||
/>
|
||||
<template v-if="isBetweenHalloweenAndHalfOfNovember(new Date())">
|
||||
<template v-if="isShowHalloween">
|
||||
<ParallaxCoverHalloween20251029 />
|
||||
</template>
|
||||
<template v-else-if="isBetweenChristmasAndHalfOfJanuary(new Date())">
|
||||
<template v-else-if="isShowChristmas">
|
||||
<Snowfall />
|
||||
<ParallaxCoverChristmas20251224 />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import type { ButtonItem } from '../theme/config.ts'
|
||||
|
||||
defineProps<{
|
||||
item: ButtonItem
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
:href="item.link"
|
||||
:class="[
|
||||
'rounded-xl px-3 py-2 lg:px-5 lg:py-3 font-extrabold outline-none backdrop-blur-md active:scale-95 focus:outline-none text-nowrap text-sm md:text-base',
|
||||
'text-slate-700 dark:text-slate-100',
|
||||
]"
|
||||
:bg="item.primary ? 'primary/20 dark:primary/30 dark:hover:primary/40' : 'black/4 dark:white/20 dark:hover:white/30'"
|
||||
transition="colors,transform duration-200 ease-in-out"
|
||||
cursor-pointer
|
||||
>
|
||||
{{ item.text }}
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type { ButtonItem } from '../theme/config.ts'
|
||||
|
||||
import HomeButton from './HomeButton.vue'
|
||||
|
||||
defineProps<{
|
||||
items: ButtonItem[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative z-10 w-full flex justify-center gap-4">
|
||||
<HomeButton v-for="b of items" :key="b.text" :item="b" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { DefaultTheme } from 'vitepress'
|
||||
|
||||
import type { ThemeConfig } from './theme/config.ts'
|
||||
|
||||
import { join, posix, resolve } from 'node:path'
|
||||
import { env } from 'node:process'
|
||||
|
||||
@@ -13,6 +15,7 @@ import { tasklist } from '@mdit/plugin-tasklist'
|
||||
import { defineConfig, postcssIsolateStyles } from 'vitepress'
|
||||
|
||||
import { version } from '../../package.json'
|
||||
import { webLive } from './constants.ts'
|
||||
import { teamMembers } from './contributors'
|
||||
import {
|
||||
discord,
|
||||
@@ -36,7 +39,7 @@ function withBase(url: string) {
|
||||
}
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
export default defineConfig<ThemeConfig>({
|
||||
cleanUrls: true,
|
||||
ignoreDeadLinks: true,
|
||||
title: projectName,
|
||||
@@ -203,6 +206,24 @@ export default defineConfig({
|
||||
link: withBase('/en/characters/'),
|
||||
},
|
||||
] as (DefaultTheme.SidebarItem & { icon?: string })[],
|
||||
|
||||
homepage: {
|
||||
buttons: [
|
||||
{
|
||||
text: 'Try Live',
|
||||
link: webLive,
|
||||
primary: true,
|
||||
},
|
||||
{
|
||||
text: 'Download',
|
||||
link: withBase('/en/docs/overview/versions'),
|
||||
},
|
||||
{
|
||||
text: 'Get Started',
|
||||
link: withBase('/en/docs/overview'),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
'zh-Hans': {
|
||||
@@ -325,6 +346,24 @@ export default defineConfig({
|
||||
link: withBase('/zh-Hans/characters/'),
|
||||
},
|
||||
] as (DefaultTheme.SidebarItem & { icon?: string })[],
|
||||
|
||||
homepage: {
|
||||
buttons: [
|
||||
{
|
||||
text: '网页版',
|
||||
link: webLive,
|
||||
primary: true,
|
||||
},
|
||||
{
|
||||
text: '下载',
|
||||
link: withBase('/zh-Hans/docs/overview/versions'),
|
||||
},
|
||||
{
|
||||
text: '使用教程',
|
||||
link: withBase('/zh-Hans/docs/overview/'),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
'ja': {
|
||||
@@ -447,6 +486,24 @@ export default defineConfig({
|
||||
link: withBase('/ja/characters/'),
|
||||
},
|
||||
] as (DefaultTheme.SidebarItem & { icon?: string })[],
|
||||
|
||||
homepage: {
|
||||
buttons: [
|
||||
{
|
||||
text: 'ライブ版を試す',
|
||||
link: webLive,
|
||||
primary: true,
|
||||
},
|
||||
{
|
||||
text: 'ダウンロード',
|
||||
link: withBase('/ja/docs/overview/versions'),
|
||||
},
|
||||
{
|
||||
text: 'はじめに',
|
||||
link: withBase('/ja/docs/overview'),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const webLive = 'https://airi.moeru.ai/'
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { DefaultTheme } from 'vitepress'
|
||||
|
||||
interface ExtraThemeConfig {
|
||||
homepage: HomePageConfig
|
||||
}
|
||||
|
||||
interface HomePageConfig {
|
||||
buttons: ButtonItem[]
|
||||
}
|
||||
|
||||
export interface ButtonItem {
|
||||
text?: string
|
||||
link?: string
|
||||
primary?: boolean
|
||||
}
|
||||
|
||||
export type ThemeConfig = DefaultTheme.Config & ExtraThemeConfig
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
layout: home
|
||||
title: 'Project AIRI: A container of souls of AI waifu / virtual characters to bring them into our worlds.'
|
||||
title: 'Project AIRI'
|
||||
slogan: 'A container of souls of AI waifu / virtual characters to bring them into our worlds.'
|
||||
---
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
layout: home
|
||||
title: 'Project AIRI: AI の「推し」やバーチャルキャラクターの魂を宿し、現実に連れてくるための器'
|
||||
title: 'Project AIRI'
|
||||
slogan: 'AI の「推し」やバーチャルキャラクターの魂を宿し、現実に連れてくるための器'
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
layout: home
|
||||
title: 'Project AIRI:
|
||||
模型驱动的灵魂容器,什么都能做一点的桌宠:让 Neuro-sama 这样的虚拟伴侣也成为我们世界中的一员吧!'
|
||||
title: 'Project AIRI'
|
||||
slogan: '模型驱动的灵魂容器,什么都能做一点的桌宠:让 Neuro-sama 这样的虚拟伴侣也成为我们世界中的一员吧!'
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user