feat(directory): add voice

This commit is contained in:
Haoyu Xu
2023-02-27 16:34:36 -05:00
parent 00e6916ad1
commit 0a320e1eb0
19 changed files with 399 additions and 123 deletions

View File

@@ -1 +1 @@
3.3.7
3.3.10

View File

@@ -5,20 +5,18 @@ import { fork } from 'child_process';
import getConfig from './libs/config.js'
import ProjectJson from './libs/project_json.js'
import EnvGenerator from './libs/env_generator.js'
import { write, rmdir, copy, writeSync, copyDir, readSync } from './libs/file.js'
import { write, rmdir, copy, writeSync, copyDir } from './libs/file.js'
import AssetsProcessor from './libs/assets_processor.js'
import init from './libs/initializer.js'
import directory from './libs/directory.js'
import { appendReadme } from './libs/append.js'
import { increase } from './libs/version.js';
import Background from './libs/background.js'
import CharwordTable from './libs/charword_table.js';
async function main() {
global.__projetRoot = path.dirname(fileURLToPath(import.meta.url))
global.__config = {
...getConfig(),
version: readSync(path.join(__projetRoot, 'Version'))
}
global.__config = getConfig()
const op = process.argv[2]
let OPERATOR_NAMES = process.argv.slice(3);
@@ -39,6 +37,7 @@ async function main() {
for (const [key, _] of Object.entries(__config.operators)) {
OPERATOR_NAMES.push(key)
}
increase(__projetRoot)
break
case 'preview':
assert(OPERATOR_NAMES.length !== 0, 'Please set the operator name.')
@@ -176,7 +175,7 @@ async function main() {
value: __config.operators[OPERATOR_NAME].link
}, {
key: "version",
value: __config.version
value: __config.version.showcase
}, {
key: "title",
value: __config.operators[OPERATOR_NAME].title

View File

@@ -21,6 +21,9 @@ share:
title:
zh-CN: "明日方舟:"
en-US: "Arknights: "
directory:
title: AKLive2D
voice: jp/CN_037.ogg
operators:
chen: !include config/chen.yaml
dusk: !include config/dusk.yaml

View File

@@ -1 +1,3 @@
VITE_APP_TITLE="AKLive2D"
VITE_APP_TITLE=AKLive2D
VITE_VERSION=0.5.5
VITE_APP_VOICE_URL=jp/CN_037.ogg

1
directory/Version Normal file
View File

@@ -0,0 +1 @@
0.5.6

View File

@@ -4,6 +4,7 @@
:root {
--text-color: rgba(255, 255, 255, 0.87);
--text-color-full: #fff;
--secondary-text-color: #686a72;
--date-color: rgba(255, 255, 255, 0.2);
--border-color: #707070;
@@ -17,7 +18,7 @@
font-family: "Geometos", "Noto Sans SC", sans-serif;
font-size: 16px;
line-height: 1.1em;
line-height: 1.2em;
font-weight: 400;
color: var(--text-color);

View File

@@ -4,13 +4,13 @@
user-select: none;
z-index: 2;
padding: 0.5rem;
cursor: pointer;
}
.dropdown .text {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
}
.dropdown .content {
@@ -18,6 +18,9 @@
}
.dropdown .icon {
position: absolute;
bottom: 0.5rem;
right: -0.1rem;
width: 0.5em;
height: 0.5em;
display: inline-block;
@@ -27,10 +30,10 @@
border-right: 0.15em solid var(--text-color);
border-top: 0.15em solid var(--text-color);
transform: translate(0, -0.15em) rotate(-45deg);
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.dropdown .icon.active {
.dropdown.active .icon,
.dropdown:hover .icon {
animation: icon-flash 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
}
@@ -55,7 +58,7 @@
visibility: hidden;
}
.dropdown .menu.active {
.dropdown.active .menu {
visibility: visible;
opacity: 1;
z-index: 2;
@@ -76,7 +79,9 @@
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.dropdown .menu .item:hover, .dropdown .menu .item:focus, .dropdown .menu .item.active {
.dropdown .menu .item:hover,
.dropdown .menu .item:focus,
.dropdown .menu .item.active {
color: var(--link-highlight-color);
}

View File

@@ -1,6 +1,5 @@
import {
useState,
useEffect
useState
} from 'react'
import './dropdown.css'
@@ -13,15 +12,15 @@ export default function Dropdown(props) {
return (
<>
<section className='dropdown'>
<section className={`dropdown ${hidden ? '' : 'active'}`} >
<section
className='text'
onClick={() => toggleDropdown()}
>
<span className='content'>{props.text}</span>
<span className={`icon ${hidden ? '' : 'active'}`}></span>
<span className='icon'></span>
</section>
<ul className={`menu ${hidden ? '' : 'active'}`}>
<ul className='menu'>
{
props.menu.map((item) => {
return (

View File

@@ -0,0 +1,62 @@
.switch {
position: relative;
user-select: none;
z-index: 2;
padding: 8px 36px 8px 8px;
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
color: var(--secondary-text-color);
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.switch.active {
color: var(--text-color);
}
.switch .content {
padding-right: 8px;
}
.switch .icon-wrapper {
color: var(--secondary-text-color);
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.switch.active .icon-wrapper {
color: var(--text-color-full);
}
.switch .icon {
position: absolute;
bottom: 8px;
right: 18px;
width: 8px;
height: 8px;
display: inline-block;
vertical-align: middle;
border-left: 2px solid currentColor;
border-bottom: 2px solid currentColor;
border-right: 2px solid currentColor;
border-top: 2px solid currentColor;
transform: translate(0, -2px) rotate(-45deg);
transition: right cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s, background-color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.switch.active .icon {
background-color: currentColor;
right: 0;
}
.switch .icon-line {
position: absolute;
bottom: 15px;
right: 6px;
width: 18px;
height: 2px;
display: inline-block;
vertical-align: middle;
background-color: currentColor;
z-index: -1;
}

View File

@@ -0,0 +1,23 @@
import { useState, useEffect } from 'react';
import './switch.css';
export default function Switch(props) {
const [on, setOn] = useState(props.on)
useEffect(() => {
setOn(props.on)
}, [props.on])
return (
<section
className={`switch ${on ? 'active' : ''}`}
onClick={() => props.handleOnClick()}
>
<span className='text'>{props.text}</span>
<section className='icon-wrapper'>
<span className='icon-line'></span>
<span className='icon'></span>
</section>
</section>
)
}

View File

@@ -31,7 +31,7 @@ export function HeaderProvider(props) {
<HeaderContext.Provider value={{
title, setTitle,
tabs, setTabs,
currentTab, setCurrentTab,
currentTab, setCurrentTab
}}>
{props.children}
</HeaderContext.Provider>

View File

@@ -3,57 +3,65 @@
"zh-CN", "en-US"
],
"key": {
"dynamic_compile": {
"zh-CN": "干员动态集录",
"en-US": "Dynamic Compile"
},
"home": {
"zh-CN": "首页",
"en-US": "Home"
},
"changelogs": {
"zh-CN": "更新日志",
"en-US": "Changelogs"
},
"offical_page": {
"zh-CN": "官方页面",
"en-US": "Offical Page"
},
"disclaimer": {
"zh-CN": "免责声明",
"en-US": "Disclaimer"
},
"disclaimer_content": {
"zh-CN": "本网站由 Halyul 设立并为明日方舟社区服务Halyul 声明本网站完全独立运营,与 上海鹰角网络科技有限公司, Esoteric Software LLC 或其任何关联实体并无任何联系。",
"en-US": "This website is set up and operated by Halyul for the benefit of the Arknights Community. Halyul hereby states that this website is dedicated, but not related to Hypergryph Co., Ltd, Esoteric Software LLC or any of its affiliated entity."
},
"privacy_policy": {
"zh-CN": "隐私政策",
"en-US": "Privacy Policy"
},
"contact_us": {
"zh-CN": "联系我们",
"en-US": "Contact Us"
},
"all": {
"zh-CN": "综合",
"en-US": "All"
},
"elite2": {
"zh-CN": "干员晋升",
"en-US": "Elite 2"
},
"skin": {
"zh-CN": "干员时装",
"en-US": "Skin"
},
"zh-CN": {
"zh-CN": "简体中文",
"en-US": "Chinese (Simplified)"
},
"en-US": {
"zh-CN": "英语",
"en-US": "English"
}
"dynamic_compile": {
"zh-CN": "动态集录",
"en-US": "Dynamic Compile"
},
"home": {
"zh-CN": "首页",
"en-US": "Home"
},
"changelogs": {
"zh-CN": "更新日志",
"en-US": "Changelogs"
},
"offical_page": {
"zh-CN": "官方页面",
"en-US": "Offical Page"
},
"disclaimer": {
"zh-CN": "免责声明",
"en-US": "Disclaimer"
},
"disclaimer_content": {
"zh-CN": "本网站由 Halyul 设立并为明日方舟社区服务Halyul 声明本网站完全独立运营,与 上海鹰角网络科技有限公司, Esoteric Software LLC 或其任何关联实体并无任何联系。",
"en-US": "This website is set up and operated by Halyul for the benefit of the Arknights Community. Halyul hereby states that this website is dedicated, but not related to Hypergryph Co., Ltd, Esoteric Software LLC or any of its affiliated entity."
},
"privacy_policy": {
"zh-CN": "隐私政策",
"en-US": "Privacy Policy"
},
"contact_us": {
"zh-CN": "联系我们",
"en-US": "Contact Us"
},
"all": {
"zh-CN": "综合",
"en-US": "All"
},
"elite2": {
"zh-CN": "干员晋升",
"en-US": "Elite 2"
},
"skin": {
"zh-CN": "干员时装",
"en-US": "Skin"
},
"voice": {
"zh-CN": "语音",
"en-US": "Voice"
},
"live2d": {
"zh-CN": "Live2D",
"en-US": "Live2D"
},
"zh-CN": {
"zh-CN": "简体中文",
"en-US": "Chinese (Simplified)"
},
"en-US": {
"zh-CN": "英语",
"en-US": "English"
}
}
}

View File

@@ -14,6 +14,7 @@
font-size: 1.5rem;
letter-spacing: 0.1rem;
flex: auto;
user-select: none;
}
.home .item-group .item {
@@ -41,7 +42,7 @@
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.home .item-group .item .item-outline{
.home .item-group .item .item-outline {
width: 100%;
height: 100%;
left: -6px;
@@ -86,11 +87,17 @@
background-color: var(--home-item-hover-background-color);
}
.home .item-group .item .item-img .live2d {
width: 100%;
height: 360px;
}
.home .item-group .item .item-info {
white-space: nowrap;
position: relative;
padding: 0.8rem 0.4rem;
line-height: 1;
line-height: 1.2em;
height: 36px;
}
.home .item-group .item .item-info .item-title-container {
@@ -109,11 +116,15 @@
}
.home .item-group .item .item-info .item-title-container .item-title {
line-height: 1.1em;
height: 1.25rem;
line-height: 1.3em;
height: auto;
}
.home .item-group .item .item-info .item-title-container .item-type {
display: flex;
flex-direction: row;
align-items: baseline;
text-align: center;
width: 1.5rem;
fill: var(--text-color)
}

View File

@@ -2,10 +2,11 @@ import {
useState,
useEffect,
useContext,
useMemo
useRef
} from 'react'
import {
Link,
useOutletContext,
} from "react-router-dom";
import './home.css'
import { ConfigContext } from '@/context/useConfigContext';
@@ -13,14 +14,19 @@ import { LanguageContext } from '@/context/useLanguageContext';
import { HeaderContext } from '@/context/useHeaderContext';
import CharIcon from '@/component/char_icon';
import MainBorder from '@/component/main_border';
import useUmami from '@parcellab/react-use-umami'
import useUmami from '@parcellab/react-use-umami';
import Switch from '@/component/switch';
import spine from '!/libs/spine-player';
import '!/libs/spine-player.css';
export default function Home() {
const _trackEvt = useUmami('/')
const { setAppbarExtraArea } = useOutletContext()
const {
setTitle,
tabs, setTabs,
currentTab, setCurrentTab } = useContext(HeaderContext)
setTabs,
currentTab, setCurrentTab
} = useContext(HeaderContext)
const { config } = useContext(ConfigContext)
const {
language,
@@ -29,6 +35,12 @@ export default function Home() {
i18n
} = useContext(LanguageContext)
const [content, setContent] = useState([])
const [voiceOn, setVoiceOn] = useState(false)
const [live2dOn, setLive2dOn] = useState(false)
const [audioUrl, setAudioUrl] = useState('')
const audioEl = new Audio(audioUrl)
const live2dRefObject = useRef({})
const live2dSpineObject = useRef({})
useEffect(() => {
setTitle('dynamic_compile')
@@ -48,20 +60,100 @@ export default function Home() {
}, [])
useEffect(() => {
const value = config.reduce((acc, cur) => {
const date = cur.date
if (acc[date]) {
acc[date].push(cur)
} else {
acc[date] = [cur]
}
return acc
}, {})
setContent(Object.values(value).sort((a, b) => new Date(b[0].date) - new Date(a[0].date)))
setContent(config?.operators || [])
}, [config])
const toggleVoice = () => {
setVoiceOn(!voiceOn)
setAudioUrl('')
}
useEffect(() => {
setAppbarExtraArea([
(
<Switch
key="voice"
text={i18n.key.voice[language]}
on={voiceOn}
handleOnClick={() => toggleVoice()}
/>
// ), (
// <Switch
// key="live2d"
// text={i18n.key.live2d[language]}
// on={live2dOn}
// handleOnClick={() => setLive2dOn(!live2dOn)}
// />
)
])
}, [voiceOn, live2dOn])
const isShown = (type) => currentTab === 'all' || currentTab === type
const playVoice = (link) => {
if (!voiceOn) return
audioEl.src = `/${link}/assets/voice/${import.meta.env.VITE_APP_VOICE_URL}`
let startPlayPromise = audioEl.play()
if (startPlayPromise !== undefined) {
startPlayPromise
.then(() => {
return
})
.catch(() => {
return
})
}
}
const getLive2d = (link) => {
const reactEl = <section className="live2d" ref={ref => {
live2dRefObject.current[link] = ref
}}></section>
return reactEl
}
useEffect(() => {
if (live2dOn) {
Object.keys(live2dRefObject.current).forEach((link) => {
const ref = live2dRefObject.current[link]
if (ref) {
if (live2dSpineObject.current[link]) {
return
}
fetch(`/_assets/dyn_portrait_char_2014_nian_nian%234.json`)
.then((res) => res.json())
.then((data) => {
live2dSpineObject.current[link] = new spine.SpinePlayer(ref, {
skelUrl: `./assets/dyn_portrait_char_2014_nian_nian%234.skel`,
atlasUrl: `./assets/dyn_portrait_char_2014_nian_nian%234.atlas`,
rawDataURIs: data,
animation: "Idle",
premultipliedAlpha: true,
alpha: true,
backgroundColor: "#00000000",
viewport: {
debugRender: false,
padLeft: `0%`,
padRight: `0%`,
padTop: `0%`,
padBottom: `0%`,
x: 0,
y: 0,
},
showControls: false,
touch: false,
fps: 60,
defaultMix: 0,
success: function (widget) {
},
})
})
}
})
}
}, [live2dOn])
return (
<section className="home">
{
@@ -71,31 +163,44 @@ export default function Home() {
<section className="item-group-wrapper" key={v[0].date} hidden={length === 0}>
<section className="item-group">
{v.map(item => {
return (<Link reloadDocument to={`/${item.link}`} className="item" key={item.link} hidden={!isShown(item.type)}>
<section className="item-background-filler" />
<section className="item-outline" />
<section className="item-img">
<img src={`/${item.link}/assets/${item.fallback_name.replace("#", "%23")}_portrait.png`} alt={item.codename[language]} />
</section>
<section className="item-info">
<section className="item-title-container">
<section className="item-title">{item.codename[language]}</section>
<section className="item-type">
<CharIcon
type={item.type}
viewBox={
item.type === 'operator' ? '0 0 88.969 71.469' : '0 0 94.563 67.437'
} />
return (
<Link
reloadDocument
to={`/${item.link}`}
className="item"
key={item.link}
hidden={!isShown(item.type)}
onMouseEnter={(e) => playVoice(item.link)}
>
<section className="item-background-filler" />
<section className="item-outline" />
<section className="item-img">
{live2dOn && item.portrait !== null ? (
getLive2d(item.link)
) : (
<img src={`/${item.link}/assets/${item.fallback_name.replace("#", "%23")}_portrait.png`} alt={item.codename[language]} />
)}
</section>
<section className="item-info">
<section className="item-title-container">
<section className="item-title">{item.codename[language]}</section>
<section className="item-type">
<CharIcon
type={item.type}
viewBox={
item.type === 'operator' ? '0 0 88.969 71.469' : '0 0 94.563 67.437'
} />
</section>
</section>
<section className="item-text-wrapper">
<span className='item-text'>{item.codename[language.startsWith("en") ? alternateLang : textDefaultLang]}</span>
</section>
<section className="item-info-background" style={{
color: item.color
}} />
</section>
<section className="item-text-wrapper">
<span className='item-text'>{item.codename[language.startsWith("en") ? alternateLang : textDefaultLang]}</span>
</section>
<section className="item-info-background" style={{
color: item.color
}} />
</section>
</Link>)
</Link>
)
})}
<section className='item-group-date'>{v[0].date}</section>
</section>

View File

@@ -25,6 +25,10 @@
justify-content: flex-start;
}
.header .spacer {
flex-grow: 1;
}
.header .dropdown {
margin-left: auto;
}
@@ -140,7 +144,7 @@
font-size: 3rem;
font-weight: 700;
text-transform: uppercase;
line-height: 1.1em;
line-height: 1.2em;
}
.main .main-header .main-tab {
@@ -174,7 +178,7 @@
}
@media (max-width: 768px) {
.main .main-header .return-button {
.main .return-button {
right: -3.4rem;
}
}

View File

@@ -27,10 +27,15 @@ export default function Root(props) {
alternateLang,
i18n
} = useContext(LanguageContext)
const { title, tabs, currentTab, setCurrentTab } = useContext(HeaderContext)
const {
title,
tabs,
currentTab, setCurrentTab
} = useContext(HeaderContext)
const [drawerDestinations, setDrawerDestinations] = useState(null)
const currentYear = new Date().getFullYear()
const [headerTabs, setHeaderTabs] = useState(null)
const [appbarExtraArea, setAppbarExtraArea] = useState(null)
const renderHeaderTabs = (tabs) => {
setHeaderTabs(tabs?.map((item) => {
@@ -111,6 +116,8 @@ export default function Root(props) {
<section className='bar'></section>
<section className='bar'></section>
</section>
<section className='spacer' />
{appbarExtraArea}
<Dropdown
text={i18n.key[language][language]}
menu={i18n.available.map((item) => {
@@ -150,7 +157,7 @@ export default function Root(props) {
}}
/>
</MainBorder>
<Outlet />
<Outlet context={{setAppbarExtraArea}}/>
</main>
<footer className='footer'>
<section className='links section'>
@@ -176,6 +183,7 @@ export default function Root(props) {
<span>Spine Runtimes © 2013 - 2019 Esoteric Software LLC</span>
<span>Assets © 2017 - {currentYear} Arknights/Hypergryph Co., Ltd</span>
<span>Source Code © 2021 - {currentYear} Halyul</span>
<span>Version: {import.meta.env.VITE_APP_VERSION}</span>
</section>
</footer>
</>

View File

@@ -28,6 +28,7 @@
},
"dependencies": {
"@parcellab/react-use-umami": "^2.0.1",
"@react-hook/cache": "^1.1.1",
"dotenv": "^16.0.3",
"node-fetch": "^3.3.0",
"react": "^18.2.0",

19
pnpm-lock.yaml generated
View File

@@ -2,6 +2,7 @@ lockfileVersion: 5.4
specifiers:
'@parcellab/react-use-umami': ^2.0.1
'@react-hook/cache': ^1.1.1
'@types/react': ^18.0.28
'@types/react-dom': ^18.0.11
'@vitejs/plugin-react-swc': ^3.2.0
@@ -18,6 +19,7 @@ specifiers:
dependencies:
'@parcellab/react-use-umami': 2.0.1_react@18.2.0
'@react-hook/cache': 1.1.1_react@18.2.0
dotenv: 16.0.3
node-fetch: 3.3.0
react: 18.2.0
@@ -242,6 +244,23 @@ packages:
react: 18.2.0
dev: false
/@react-hook/cache/1.1.1_react@18.2.0:
resolution: {integrity: sha512-FY0Bwvxsv9Gu6pUbVdNylh4jyWPtQRegQM9G6yVx3l2cYjbxzaJwEXticLfsRcciCbnk7iyZfJn6mZ4N4+q9qA==}
peerDependencies:
react: '>=16.8'
dependencies:
'@react-hook/latest': 1.0.3_react@18.2.0
react: 18.2.0
dev: false
/@react-hook/latest/1.0.3_react@18.2.0:
resolution: {integrity: sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==}
peerDependencies:
react: '>=16.8'
dependencies:
react: 18.2.0
dev: false
/@remix-run/router/1.3.2:
resolution: {integrity: sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==}
engines: {node: '>=14'}

View File

@@ -4,7 +4,9 @@ import { defineConfig } from 'vite'
import assert from 'assert'
import react from '@vitejs/plugin-react-swc'
import getConfig from './libs/config.js'
import { rmdir } from './libs/file.js'
import { rmdir, writeSync } from './libs/file.js'
import { increase } from './libs/version.js'
import EnvGenerator from './libs/env_generator.js'
global.__projetRoot = path.dirname(fileURLToPath(import.meta.url))
@@ -28,7 +30,10 @@ class ViteRunner {
this.#mode = temp[0] === "vite" ? temp[1] : process.argv[2]
switch (this.#mode) {
case 'directory':
result = this.#directoryConfig
result = {
data: this.#directoryConfig,
versionDir: path.join(__projetRoot, "directory"),
}
const op = temp[2] || process.argv[3]
if (op !== 'preview') {
rmdir(path.resolve(__projetRoot, this.#globalConfig.folder.release, "_directory"))
@@ -39,7 +44,10 @@ class ViteRunner {
case 'build':
case 'build-all':
case 'preview':
result = this.#operatorConfig
result = {
data: this.#operatorConfig,
versionDir: path.join(__projetRoot),
}
break
default:
return
@@ -48,12 +56,14 @@ class ViteRunner {
}
start() {
const viteConfig = this.config;
const configObj = this.config
const viteConfig = configObj.data;
switch (this.#mode) {
case 'dev':
this.#dev(viteConfig)
break
case 'build':
increase(configObj.versionDir)
case 'build-all':
this.#build(viteConfig)
break
@@ -122,7 +132,22 @@ class ViteRunner {
}
get #directoryConfig() {
if (process.env.npm_lifecycle_event === 'vite:directory:build') {
increase(path.join(__projetRoot, "directory"))
}
const directoryDir = path.resolve(__projetRoot, 'directory')
writeSync((new EnvGenerator()).generate([
{
key: "app_title",
value: this.#globalConfig.directory.title
}, {
key: "version",
value: this.#globalConfig.version.directory
}, {
key: "app_voice_url",
value: this.#globalConfig.directory.voice
}
]), path.join(directoryDir, '.env'))
this.#mode = process.argv[3]
return {
...this.#baseViteConfig,
@@ -136,6 +161,7 @@ class ViteRunner {
resolve: {
alias: {
'@': path.resolve(directoryDir, './src'),
'!': path.resolve(__projetRoot, './src'),
},
},
build: {
@@ -151,7 +177,6 @@ class ViteRunner {
},
}
}
}
async function main() {
@@ -162,4 +187,4 @@ async function main() {
main()
export default defineConfig((new ViteRunner()).config)
export default defineConfig((new ViteRunner()).config.data)