refactor(directory): rewrite stylesheet with scss

This commit is contained in:
Haoyu Xu
2023-03-14 21:35:19 -04:00
parent 0da4337454
commit cf933d6a56
37 changed files with 526 additions and 800 deletions

View File

@@ -1,15 +0,0 @@
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": [
'eslint:recommended',
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
}

15
.eslintrc.json Normal file
View File

@@ -0,0 +1,15 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
}
}

View File

@@ -1 +1 @@
1.0.17 1.0.18

View File

@@ -2,40 +2,20 @@ import React from 'react';
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import { import {
createBrowserRouter, createBrowserRouter,
createRoutesFromElements,
RouterProvider, RouterProvider,
Route
} from "react-router-dom"; } from "react-router-dom";
import Root from "@/routes/root"; import Root from "@/routes/Root";
import ErrorPage from "@/routes/error-page"; import Error from "@/routes/Error";
import routes from "@/routes"; import routes from "@/routes";
import '@/App.css'; import '@/App.scss';
import 'reset-css'; import 'reset-css';
const router = createBrowserRouter( const router = createBrowserRouter([{
createRoutesFromElements( path: "/",
<Route element: <Root />,
path="/" errorElement: <Error />,
element={ children: routes.filter((item) => item.routeable),
<Root },]);
title={import.meta.env.VITE_APP_TITLE}
/>
}
errorElement={<ErrorPage />}
>
{routes.map((route) => {
return (
<Route key={route.name}
index={route.index}
path={route.path}
element={route.element}
loader={route.loader}
/>
)
})}
</Route>
)
);
ReactDOM.createRoot(document.getElementById('root')).render( ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode> <React.StrictMode>

View File

@@ -3,17 +3,17 @@
@import 'https://fonts.cdnfonts.com/css/geometos'; @import 'https://fonts.cdnfonts.com/css/geometos';
:root { :root {
--text-color: rgba(255 255 255 87%); --text-color: rgba(255, 255, 255, 87%);
--text-color-full: #fff; --text-color-full: #fff;
--secondary-text-color: #686a72; --secondary-text-color: #686a72;
--date-color: rgba(255 255 255 20%); --date-color: rgba(255, 255, 255, 20%);
--border-color: #707070; --border-color: #707070;
--link-highlight-color: #33b5e5; --link-highlight-color: #33b5e5;
--drawer-background-color: rgba(0 0 0 88%); --drawer-background-color: rgba(0, 0, 0, 88%);
--root-background-color: #131313; --root-background-color: #131313;
--home-item-hover-background-color: rgba(67 67 67 30%); --home-item-hover-background-color: rgba(67, 67, 67, 30%);
--home-item-background-linear-gradient-color: rgba(255 255 255 10%); --home-item-background-linear-gradient-color: rgba(255, 255, 255, 10%);
--home-item-outline-color: rgba(214 214 214 30%); --home-item-outline-color: rgba(214, 214, 214, 30%);
--button-color: #666; --button-color: #666;
font-family: Geometos, "Noto Sans SC", sans-serif; font-family: Geometos, "Noto Sans SC", sans-serif;

View File

@@ -1,14 +1,14 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classes from './main_border.module.scss'; import classes from './scss/border.module.scss';
export default function MainBorder(props) { export default function Border(props) {
return ( return (
<section className={classes.border}> <section className={classes.border}>
{props.children} {props.children}
</section> </section>
) )
} }
MainBorder.propTypes = { Border.propTypes = {
children: PropTypes.node, children: PropTypes.node,
}; };

View File

@@ -2,7 +2,7 @@ import React, {
useState useState
} from 'react' } from 'react'
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classes from './dropdown.module.scss' import classes from './scss/dropdown.module.scss'
export default function Dropdown(props) { export default function Dropdown(props) {
const [hidden, setHidden] = useState(true) const [hidden, setHidden] = useState(true)

View File

@@ -1,9 +1,9 @@
import React, { import React, {
useState, useState,
} from 'react' } from 'react'
import classes from './popup.module.scss'; import classes from './scss/popup.module.scss';
import ReturnButton from '@/component/return_button'; import ReturnButton from '@/component/return_button';
import MainBorder from '@/component/main_border'; import Border from '@/component/border';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
export default function Popup(props) { export default function Popup(props) {
@@ -20,7 +20,7 @@ export default function Popup(props) {
<section className={classes.text}>{props.title}</section> <section className={classes.text}>{props.title}</section>
<ReturnButton onClick={toggle} className={classes["return-button"]} /> <ReturnButton onClick={toggle} className={classes["return-button"]} />
</section> </section>
<MainBorder /> <Border />
<section className={classes.content}> <section className={classes.content}>
{props.children} {props.children}
</section> </section>

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classes from './return_button.module.scss' import classes from './scss/return_button.module.scss'
export default function ReturnButton(props) { export default function ReturnButton(props) {

View File

@@ -68,7 +68,7 @@
&:hover, &:hover,
&:focus, &:focus,
&:active { &.active {
.text { .text {
color: currentColor; color: currentColor;
} }

View File

@@ -41,6 +41,13 @@
align-items: center; align-items: center;
text-transform: uppercase; text-transform: uppercase;
font-family: "Geometos", "Noto Sans SC", sans-serif; font-family: "Geometos", "Noto Sans SC", sans-serif;
.return-button {
color: var(--button-color);
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
&:hover {
color: var(--text-color);
}
}
} }
.text { .text {
flex-grow: 1; flex-grow: 1;
@@ -66,13 +73,6 @@
opacity: 0.5; opacity: 0.5;
visibility: visible; visibility: visible;
} }
.return-button {
color: var(--button-color);
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
&:hover {
color: var(--text-color);
}
}
} }
&.active { &.active {
opacity: 1; opacity: 1;

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classes from './switch.module.scss'; import classes from './scss/switch.module.scss';
import { import {
useI18n useI18n
} from '@/state/language' } from '@/state/language'

View File

@@ -9,7 +9,8 @@ import {
useNavigate, useNavigate,
useRouteError useRouteError
} from "react-router-dom"; } from "react-router-dom";
import './error-page.css' import header from '@/scss/root/header.module.scss'
import classes from '@/scss/error/Error.module.scss'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils';
import Switch from '@/component/switch'; import Switch from '@/component/switch';
@@ -27,7 +28,8 @@ const obj = config.files[Math.floor((Math.random() * config.files.length))]
const filename = obj.key.replace("#", "%23") const filename = obj.key.replace("#", "%23")
const padding = obj.paddings const padding = obj.paddings
export default function ErrorPage() { export default function Error() {
// eslint-disable-next-line no-unused-vars
const _trackEvt = useUmami('/error') const _trackEvt = useUmami('/error')
const error = useRouteError(); const error = useRouteError();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -52,8 +54,8 @@ export default function ErrorPage() {
useEffect(() => { useEffect(() => {
console.log(error) console.log(error)
fetch(`/${import.meta.env.VITE_DIRECTORY_FOLDER}/${filename}.json`).then(res => res.json()).then(data => { fetch(`/${import.meta.env.VITE_DIRECTORY_FOLDER}/${filename}.json`).then(res => res.json()).then(data => {
setSpineData(data) setSpineData(data)
}) })
}, [error]) }, [error])
useEffect(() => { useEffect(() => {
@@ -130,10 +132,9 @@ export default function ErrorPage() {
}, [playVoice, spineData]); }, [playVoice, spineData]);
return ( return (
<section className='error-page'> <section className={classes.error}>
<header className='header'> <header className={`${header.header} ${classes.header}`}>
<ReturnButton <ReturnButton
className='return-button'
onClick={() => navigate(-1, { replace: true })} onClick={() => navigate(-1, { replace: true })}
/> />
<Switch <Switch
@@ -143,11 +144,11 @@ export default function ErrorPage() {
handleOnClick={() => setVoiceOn(!voiceOn)} handleOnClick={() => setVoiceOn(!voiceOn)}
/> />
</header> </header>
<main className='main'> <main className={classes.main}>
{ {
content.map((item, index) => { content.map((item, index) => {
return ( return (
<section key={index} className='content'> <section key={index} className={classes.content}>
<Typewriter <Typewriter
words={[item]} words={[item]}
cursor cursor
@@ -159,7 +160,7 @@ export default function ErrorPage() {
}) })
} }
<section <section
className={`spine ${spineDone ? 'active' : ''}`} className={`${classes.spine} ${spineDone ? classes.active : ''}`}
ref={spineRef} ref={spineRef}
/> />
</main> </main>

View File

@@ -10,9 +10,12 @@ import {
Link, Link,
NavLink, NavLink,
useNavigate, useNavigate,
ScrollRestoration, ScrollRestoration
} from "react-router-dom"; } from "react-router-dom";
import './root.css' import classes from '@/scss/root/Root.module.scss'
import header from '@/scss/root/header.module.scss'
import footer from '@/scss/root/footer.module.scss'
import drawer from '@/scss/root/drawer.module.scss'
import routes from '@/routes' import routes from '@/routes'
import { useConfig } from '@/state/config'; import { useConfig } from '@/state/config';
import { useHeader } from '@/state/header'; import { useHeader } from '@/state/header';
@@ -25,7 +28,7 @@ import { useBackgrounds } from '@/state/background';
import Dropdown from '@/component/dropdown'; import Dropdown from '@/component/dropdown';
import Popup from '@/component/popup'; import Popup from '@/component/popup';
import ReturnButton from '@/component/return_button'; import ReturnButton from '@/component/return_button';
import MainBorder from '@/component/main_border'; import Border from '@/component/border';
import CharIcon from '@/component/char_icon'; import CharIcon from '@/component/char_icon';
const currentYear = new Date().getFullYear() const currentYear = new Date().getFullYear()
@@ -77,39 +80,39 @@ export default function Root() {
return ( return (
<> <>
<header className='header'> <header className={header.header}>
<section <section
className={`navButton ${drawerHidden ? '' : 'active'}`} className={`${header.navButton} ${drawerHidden ? '' : header.active}`}
onClick={() => toggleDrawer()} onClick={() => toggleDrawer()}
> >
<section className='bar'></section> <section className={header.bar} />
<section className='bar'></section> <section className={header.bar} />
<section className='bar'></section> <section className={header.bar} />
</section> </section>
<section className='spacer' /> <section className={header.spacer} />
<section className='extra-area'> <section className={header['extra-area']}>
{extraArea} {extraArea}
<LanguageDropdown /> <LanguageDropdown />
</section> </section>
</header> </header>
<nav className={`drawer ${drawerHidden ? '' : 'active'}`}> <nav className={`${drawer.drawer} ${drawerHidden ? '' : drawer.active}`}>
<section <section
className='links' className={drawer.links}
> >
<DrawerDestinations <DrawerDestinations
toggleDrawer={toggleDrawer} toggleDrawer={toggleDrawer}
/> />
</section> </section>
<section <section
className={`overlay ${drawerHidden ? '' : 'active'}`} className={`${drawer.overlay} ${drawerHidden ? '' : drawer.active}`}
onClick={() => toggleDrawer()} onClick={() => toggleDrawer()}
/> />
</nav> </nav>
<main className='main'> <main className={classes.main}>
<section className='main-header'> <section className={classes.header}>
<section className='main-title'> <section className={classes.title}>
{headerIcon && ( {headerIcon && (
<section className='main-icon'> <section className={classes.icon}>
<CharIcon <CharIcon
type={headerIcon} type={headerIcon}
viewBox={ viewBox={
@@ -120,7 +123,7 @@ export default function Root() {
)} )}
{title} {title}
</section> </section>
<section className='main-tab'> <section className={classes.tab}>
{headerTabs} {headerTabs}
</section> </section>
</section> </section>
@@ -140,32 +143,32 @@ function FooterElement() {
return useMemo(() => { return useMemo(() => {
return ( return (
<footer className='footer'> <footer className={footer.footer}>
<section className='links section'> <section className={`${footer.links} ${footer.section}`}>
<section className="item"> <section className={footer.item}>
<Popup <Popup
className='link' className={footer.link}
title={i18n('disclaimer')} title={i18n('disclaimer')}
> >
{i18n('disclaimer_content')} {i18n('disclaimer_content')}
</Popup> </Popup>
</section> </section>
<section className="item"> <section className={footer.item}>
<Link reloadDocument to="https://privacy.halyul.dev" target="_blank" className='link'>{i18n('privacy_policy')}</Link> <Link reloadDocument to="https://privacy.halyul.dev" target="_blank" className={footer.link}>{i18n('privacy_policy')}</Link>
</section> </section>
<section className="item"> <section className={footer.item}>
<Link reloadDocument to="https://github.com/Halyul/aklive2d" target="_blank" className='link'>GitHub</Link> <Link reloadDocument to="https://github.com/Halyul/aklive2d" target="_blank" className={footer.link}>GitHub</Link>
</section> </section>
<section className="item"> <section className={footer.item}>
<Popup <Popup
className='link' className={footer.link}
title={i18n('contact_us')} title={i18n('contact_us')}
> >
ak#halyul.dev ak#halyul.dev
</Popup> </Popup>
</section> </section>
</section> </section>
<section className='copyright section' onDoubleClick={() => { <section className={`${footer.copyright} ${footer.section}`} onDoubleClick={() => {
navigate('/error') navigate('/error')
}}> }}>
<span>Spine Runtimes © 2013 - 2019 Esoteric Software LLC</span> <span>Spine Runtimes © 2013 - 2019 Esoteric Software LLC</span>
@@ -191,7 +194,7 @@ function DrawerDestinations({ toggleDrawer }) {
key={item.name} key={item.name}
to={item.path} to={item.path}
target="_blank" target="_blank"
className="link" className={drawer.link}
onClick={() => toggleDrawer(false)} onClick={() => toggleDrawer(false)}
> >
<section> <section>
@@ -207,7 +210,9 @@ function DrawerDestinations({ toggleDrawer }) {
<NavLink <NavLink
to={item.path} to={item.path}
key={item.name} key={item.name}
className="link" className={({ isActive, }) =>
`${drawer.link} ${isActive ? drawer.active : ''}`
}
onClick={() => toggleDrawer(false)} onClick={() => toggleDrawer(false)}
> >
<section> <section>
@@ -253,15 +258,15 @@ function HeaderTabsElement({ item }) {
return ( return (
<section <section
className={`main-tab-item ${currentTab === item.key ? 'active' : ''}`} className={`${classes.item} ${currentTab === item.key ? classes.active : ''}`}
onClick={(e) => { onClick={(e) => {
setCurrentTab(item.key) setCurrentTab(item.key)
item.onClick && item.onClick(e, currentTab) item.onClick && item.onClick(e, currentTab)
}} }}
style={item.style} style={item.style}
> >
<section className='main-tab-text-wrapper'> <section className={classes['text-wrapper']}>
<span className='text'>{i18n(item.key)}</span> <span>{i18n(item.key)}</span>
</section> </section>
</section> </section>
) )
@@ -275,12 +280,12 @@ function HeaderReturnButton() {
return useMemo(() => { return useMemo(() => {
return ( return (
<MainBorder> <Border>
<ReturnButton <ReturnButton
className='return-button' className={classes['return-button']}
onClick={() => navigate("/")} onClick={() => navigate("/")}
/> />
</MainBorder> </Border>
) )
}, [navigate]) }, [navigate])
} }

View File

@@ -1,53 +0,0 @@
.error-page {
min-height: 100vh;
display: flex;
flex-direction: column;
font-size: 16px;
user-select: none;
}
.error-page .header {
padding: 1rem;
justify-content: space-between;
}
.error-page .main {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding-top: 10rem;
font-size: 3rem;
gap: 2rem;
}
.error-page .spine {
max-width: 600px;
flex: 1;
visibility: hidden;
opacity: 0;
}
.error-page .spine.active {
visibility: visible;
opacity: 1;
}
@media (max-width: 768px) {
.error-page .main {
padding-top: 6rem;
}
.error-page .content {
font-size: 2rem;
}
}
@media (max-width: 480px) {
.error-page .main {
padding-top: 4rem;
}
.error-page .content {
font-size: 1.5rem;
}
}

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import Home from "@/routes/path/home"; import Home from "@/routes/path/Home";
import Operator from "@/routes/path/operator"; import Operator from "@/routes/path/Operator";
import Changelogs from "@/routes/path/changelogs"; import Changelogs from "@/routes/path/Changelogs";
export default [ export default [
{ {
@@ -9,24 +9,28 @@ export default [
index: true, index: true,
name: "home", name: "home",
element: <Home />, element: <Home />,
inDrawer: true inDrawer: true,
routeable: true
}, { }, {
path: "changelogs", path: "changelogs",
index: false, index: false,
name: "changelogs", name: "changelogs",
element: <Changelogs />, element: <Changelogs />,
inDrawer: true inDrawer: true,
routeable: true
}, { }, {
path: "https://ak.hypergryph.com/archive/dynamicCompile/", path: "https://ak.hypergryph.com/archive/dynamicCompile/",
index: false, index: false,
name: "offical_page", name: "offical_page",
element: <a/>, element: <a/>,
inDrawer: true inDrawer: true,
routeable: false
}, { }, {
path: ":key", path: ":key",
index: false, index: false,
name: "operator", name: "operator",
element: <Operator />, element: <Operator />,
inDrawer: false inDrawer: false,
routeable: true
}, },
] ]

View File

@@ -3,11 +3,11 @@ import React, {
useEffect, useEffect,
useMemo useMemo
} from 'react' } from 'react'
import classes from './changelogs.module.scss' import classes from '@/scss/changelogs/Changelogs.module.scss'
import { useHeader } from '@/state/header'; import { useHeader } from '@/state/header';
import { useAppbar } from '@/state/appbar'; import { useAppbar } from '@/state/appbar';
import useUmami from '@parcellab/react-use-umami' import useUmami from '@parcellab/react-use-umami'
import MainBorder from '@/component/main_border'; import Border from '@/component/border';
export default function Changelogs() { export default function Changelogs() {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
@@ -59,7 +59,7 @@ export default function Changelogs() {
</section> </section>
<section className={classes.date}>{item.date}</section> <section className={classes.date}>{item.date}</section>
</section> </section>
<MainBorder /> <Border />
</section> </section>
) )
}) })

View File

@@ -8,7 +8,7 @@ import PropTypes from 'prop-types';
import { import {
NavLink, NavLink,
} from "react-router-dom"; } from "react-router-dom";
import classes from './home.module.scss' import classes from '@/scss/home/Home.module.scss'
import { useConfig } from '@/state/config'; import { useConfig } from '@/state/config';
import { import {
useLanguage useLanguage
@@ -19,7 +19,7 @@ import useAudio from '@/libs/voice';
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils';
import CharIcon from '@/component/char_icon'; import CharIcon from '@/component/char_icon';
import MainBorder from '@/component/main_border'; import Border from '@/component/border';
import useUmami from '@parcellab/react-use-umami'; import useUmami from '@parcellab/react-use-umami';
import Switch from '@/component/switch'; import Switch from '@/component/switch';
@@ -76,7 +76,7 @@ export default function Home() {
})} })}
<section className={classes.date}>{v[0].date}</section> <section className={classes.date}>{v[0].date}</section>
</section> </section>
<MainBorder /> <Border />
</section> </section>
) )
}) })

View File

@@ -10,7 +10,7 @@ import {
useNavigate, useNavigate,
Link Link
} from "react-router-dom"; } from "react-router-dom";
import './operator.css' import classes from '@/scss/operator/Operator.module.scss'
import { useConfig } from '@/state/config'; import { useConfig } from '@/state/config';
import { import {
useLanguage, useLanguage,
@@ -22,7 +22,7 @@ import useAudio from '@/libs/voice';
import useUmami from '@parcellab/react-use-umami' import useUmami from '@parcellab/react-use-umami'
import spine from '!/libs/spine-player' import spine from '!/libs/spine-player'
import '!/libs/spine-player.css' import '!/libs/spine-player.css'
import MainBorder from '@/component/main_border'; import Border from '@/component/border';
import { useI18n } from '@/state/language'; import { useI18n } from '@/state/language';
const getVoiceFoler = (lang) => { const getVoiceFoler = (lang) => {
@@ -341,9 +341,9 @@ export default function Operator() {
} }
return ( return (
<section className="operator"> <section className={classes.operator}>
<section className="spine-player-wrapper"> <section className={classes.main}>
<section className="spine-settings" style={{ <section className={classes.settings} style={{
color: config?.color color: config?.color
}}> }}>
{ {
@@ -351,17 +351,17 @@ export default function Operator() {
if (item.options.length === 0) return null if (item.options.length === 0) return null
return ( return (
<section key={item.name}> <section key={item.name}>
<section className='settings-title-wrapper'> <section className={classes.title}>
<section className='text'>{i18n(item.name)}</section> <section className={classes.text}>{i18n(item.name)}</section>
</section> </section>
<section className='settings-content-wrapper styled-selection'> <section className={classes['styled-selection']}>
{item.options.map((option) => { {item.options.map((option) => {
return ( return (
<section className={`content ${option.activeRule && option.activeRule() ? 'active' : ''}`} onClick={(e) => option.onClick(e)} key={option.name}> <section className={`${classes.content} ${option.activeRule && option.activeRule() ? classes.active : ''}`} onClick={(e) => option.onClick(e)} key={option.name}>
<section className='content-text'> <section className={classes.option}>
<section className="outline" /> <section className={classes.outline} />
<section className='text'>{i18n(option.name)}</section> <section className={classes.text}>{i18n(option.name)}</section>
<section className='tick-icon' /> <section className={classes['tick-icon']} />
</section> </section>
</section> </section>
) )
@@ -372,23 +372,22 @@ export default function Operator() {
}) })
} }
<section> <section>
<section className='settings-title-wrapper'> <section className={classes.title}>
<section className='text'>{i18n('external_links')}</section> <section className={classes.text}>{i18n('external_links')}</section>
</section> </section>
<section className='settings-content-wrapper styled-selection'> <section className={classes['styled-selection']}>
<Link <Link
reloadDocument reloadDocument
to={`./index.html?settings`} to={`./index.html?settings`}
target='_blank' target='_blank'
className='extra-links-item'
style={{ style={{
color: config?.color color: config?.color
}} }}
> >
<section className='content'> <section className={classes.content}>
<section className='content-text'> <section className={classes.option}>
<section className="outline" /> <section className={classes.outline} />
<section className='text'> <section className={classes.text}>
{i18n('web_version')} {i18n('web_version')}
</section> </section>
</section> </section>
@@ -400,14 +399,13 @@ export default function Operator() {
reloadDocument reloadDocument
to={`https://steamcommunity.com/sharedfiles/filedetails/?id=${config.workshopId}`} to={`https://steamcommunity.com/sharedfiles/filedetails/?id=${config.workshopId}`}
target='_blank' target='_blank'
className='extra-links-item'
style={{ style={{
color: config?.color color: config?.color
}}> }}>
<section className='content'> <section className={classes.content}>
<section className='content-text'> <section className={classes.option}>
<section className="outline" /> <section className={classes.outline} />
<section className='text'> <section className={classes.text}>
{i18n('steam_workshop')} {i18n('steam_workshop')}
</section> </section>
</section> </section>
@@ -418,27 +416,27 @@ export default function Operator() {
</section> </section>
</section> </section>
</section> </section>
<section className="spine-container" style={currentBackground && { <section className={classes.container} style={currentBackground && {
backgroundImage: `url(/${key}/assets/${import.meta.env.VITE_BACKGROUND_FOLDER}/${currentBackground})` backgroundImage: `url(/${key}/assets/${import.meta.env.VITE_BACKGROUND_FOLDER}/${currentBackground})`
}} > }} >
{ {
config && ( config && (
<img src={`/${config.link}/assets/${config.logo}.png`} alt={config?.codename[language]} className='operator-logo' /> <img src={`/${config.link}/assets/${config.logo}.png`} alt={config?.codename[language]} className={classes.logo} />
) )
} }
<section ref={spineRef} /> <section ref={spineRef} className={classes.wrapper} />
{currentVoiceId && subtitleObj && ( {currentVoiceId && subtitleObj && (
<section className={`voice-wrapper${hideSubtitle ? '' : ' active'}`}> <section className={`${classes.voice} ${hideSubtitle ? '' : classes.active }`}>
<section className='voice-title'>{subtitleObj[currentVoiceId]?.title}</section> <section className={classes.type}>{subtitleObj[currentVoiceId]?.title}</section>
<section className='voice-subtitle'> <section className={classes.subtitle}>
<span>{subtitleObj[currentVoiceId]?.text}</span> <span>{subtitleObj[currentVoiceId]?.text}</span>
<span className='voice-triangle' /> <span className={classes.triangle} />
</section> </section>
</section>) </section>)
} }
</section> </section>
</section> </section>
<MainBorder /> <Border />
</section> </section>
) )
} }

View File

@@ -1 +0,0 @@
@use 'tab_base';

View File

@@ -1,241 +0,0 @@
.operator .spine-player-wrapper {
padding: 3rem 0 2rem 0;
display: flex;
justify-content: space-between;
}
.operator .spine-container {
background-position: center;
background-repeat: no-repeat;
background-size: cover;
width: 100%;
position: relative;
margin-bottom: 2rem;
user-select: none;
}
.operator .spine-container:before {
content: "";
display: block;
padding-top: 100%;
}
.operator .spine-container .spine-player {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.operator .spine-settings {
margin-right: 1.5rem;
user-select: none;
}
.operator .text {
color: var(--text-color);
}
.operator .spine-settings .settings-title-wrapper {
font-size: 1.25rem;
border-left: 3px solid currentColor;
padding-left: 0.75rem;
margin-bottom: 0.8rem;
}
.operator .styled-selection {
margin-bottom: 0.8rem;
}
.operator .styled-selection .content {
padding: 0.8rem 0;
cursor: pointer;
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.operator .styled-selection .content .content-text {
pointer-events: none;
position: relative;
transform: translate3d(0, 0, 1px);
font-size: 1rem;
padding: 0.44rem 2.25rem 0.44rem 0.63rem;
background-color: var(--home-item-hover-background-color);
background-image: repeating-linear-gradient(
90deg,
var(--home-item-background-linear-gradient-color) 0,
var(--home-item-background-linear-gradient-color) 1px,
transparent 1px,
transparent 4px
);
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.operator .styled-selection .content:hover,
.operator .styled-selection .content.active {
transform: translate3d(6px, 0, 1px);
}
.operator .styled-selection .content .content-text .outline {
width: 100%;
height: 100%;
left: -6px;
top: -6px;
border: var(--home-item-outline-color) 1px dashed;
padding: 6px;
}
.operator .styled-selection .content .content-text::before,
.operator .styled-selection .content .content-text .outline {
content: "";
display: block;
position: absolute;
z-index: -1;
opacity: 0;
visibility: hidden;
transition: opacity cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s,
visibility cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.operator .styled-selection .content .content-text .outline::before,
.operator .styled-selection .content .content-text .outline::after {
content: "";
display: block;
position: absolute;
left: -2px;
height: 2px;
width: 100%;
border-left: var(--text-color) solid 2px;
border-right: var(--text-color) solid 2px;
}
.operator .styled-selection .content .content-text .outline::before {
top: -2px;
}
.operator .styled-selection .content .content-text .outline::after {
bottom: -2px;
}
.operator .styled-selection .content .content-text::before {
right: 0;
top: 0;
width: 60%;
height: 100%;
background-image: linear-gradient(90deg, transparent, currentColor);
}
.operator .styled-selection .content:hover .content-text::before,
.operator .styled-selection .content.active .content-text::before,
.operator .styled-selection .content:hover .content-text .outline,
.operator .styled-selection .content.active .content-text .outline,
.operator .styled-selection .content.active .content-text .tick-icon {
opacity: 1;
visibility: visible;
}
.operator .styled-selection .content .content-text .tick-icon {
display: inline-block;
position: absolute;
z-index: 0;
right: 0.31rem;
top: 50%;
width: 0.5rem;
height: 1rem;
opacity: 0;
visibility: hidden;
transition: opacity cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s,
visibility cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
border-right: var(--text-color) solid 0.25rem;
border-bottom: var(--text-color) solid 0.25rem;
transform: translate(-50%, -70%) rotate(45deg);
}
.operator .voice-wrapper {
position: absolute;
left: 0;
bottom: 30%;
z-index: 1;
max-width: 480px;
width: 85%;
opacity: 0;
margin: 16px;
transition: all 0.5s cubic-bezier(0.65, 0.05, 0.36, 1);
visibility: hidden;
font-family: 'Noto Sans', sans-serif;
}
.operator .voice-wrapper.active {
opacity: 1;
visibility: visible;
}
.operator .voice-wrapper .voice-title {
background-color: #9e9e9e;
color: #000;
display: inline-block;
position: absolute;
top: -12px;
left: -8px;
padding: 2px 8px;
font-size: 14px;
max-width: 180px;
width: 65%;
box-shadow: 0 3px 6px #00000080;
z-index: 1;
}
.operator .voice-wrapper .voice-subtitle {
background-color: #000000a6;
color: #fff;
padding: 16px;
font-size: 18px;
box-shadow: 0 6px 12px #00000080;
position: relative;
word-break: break-word;
}
.operator .voice-wrapper .voice-triangle {
position: absolute;
bottom: 0px;
right: 8px;
width: 0;
height: 0;
border-style: solid;
border-width: 8px 8px 8px 8px;
border-color: white transparent transparent transparent;
}
.operator .operator-logo {
position: absolute;
top: 0;
left: 0;
width: 30%;
height: auto;
opacity: 0.3;
}
@media (max-width: 1280px) {
.operator .spine-container {
width: 100%;
position: relative;
margin-bottom: 2rem;
}
.operator .spine-container:before {
content: "";
display: block;
padding-top: 100%;
}
.operator .spine-container .spine-player {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.operator .spine-player-wrapper {
flex-direction: column-reverse;
}
.operator .voice-wrapper {
bottom: 0;
}
}

View File

@@ -1,294 +0,0 @@
.main {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
padding-bottom: 3rem;
margin: 0 auto;
width: 70%;
max-width: 100rem;
padding-top: 5rem;
}
.header {
width: auto;
position: fixed;
left: 0;
top: 0;
right: 0;
padding: 1rem;
z-index: 3;
height: 3rem;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
justify-content: flex-start;
pointer-events: none;
}
.header .spacer {
flex-grow: 1;
}
.header .dropdown {
margin-left: auto;
}
.navButton {
padding: 0.5rem;
font-size: 2rem;
width: 2rem;
height: 2rem;
cursor: pointer;
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column;
z-index: 2;
}
.navButton .bar {
width: 2rem;
height: 0.2rem;
background-color: var(--text-color);
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.navButton .bar:nth-child(1) {
transform: translate(0, -200%);
}
.navButton .bar:nth-child(3) {
transform: translate(0, 200%);
}
.navButton.active .bar:nth-child(1) {
transform: translate(0, 100%) rotateZ(45deg) scaleX(0.5) translate(-50%);
}
.navButton.active .bar:nth-child(2) {
transform: rotateZ(-45deg);
}
.navButton.active .bar:nth-child(3) {
transform: translate(0, -100%) rotateZ(45deg) scaleX(0.5) translate(50%);
}
.extra-area {
display: flex;
flex-direction: row;
align-items: center;
}
.navButton,
.extra-area {
pointer-events: auto;
}
.drawer {
position: fixed;
top: 0;
left: -15rem;
width: 15rem;
height: 100%;
z-index: 1;
pointer-events: none;
transition: left cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
text-align: center;
display: flex;
flex-direction: row;
align-items: flex-start;
user-select: none;
}
.drawer .links {
padding: 8rem 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
background-color: var(--drawer-background-color);
height: 100%;
width: 15rem;
}
.drawer .overlay {
height: 100%;
flex-grow: 1;
z-index: 2;
}
.drawer.active {
pointer-events: all;
left: 0;
width: 100vw;
}
.drawer .link {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.2rem;
color: var(--text-color);
font-size: 0.8rem;
font-weight: 500;
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
text-transform: uppercase;
}
.drawer .link:hover {
color: var(--link-highlight-color);
}
.drawer .link.active {
color: var(--link-highlight-color);
}
.main .main-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
flex-wrap: wrap;
position: relative;
padding-right: 2rem;
}
.main .main-header .main-title {
font-size: 3rem;
font-weight: 700;
text-transform: uppercase;
line-height: 1.2em;
}
@media (max-width: 600px) {
.main .main-header .main-title {
font-size: 2.5rem;
}
}
@media (max-width: 480px) {
.main .main-header .main-title {
font-size: 2rem;
}
}
.main .main-header .main-tab {
flex: auto;
white-space: pre;
user-select: none;
display: flex;
flex-direction: row;
justify-content: flex-end;
overflow: hidden;
z-index: 2;
}
.main .main-header .main-tab .main-tab-item {
font-size: 1.25rem;
line-height: 3em;
font-weight: 700;
padding: 0 1rem;
text-transform: uppercase;
cursor: pointer;
border-bottom: 0.3rem solid transparent;
display: inline-block;
cursor: pointer;
text-decoration: none;
overflow: hidden;
text-overflow: ellipsis;
}
.main .main-header .main-tab .main-tab-item .main-tab-text-wrapper {
overflow: hidden;
text-overflow: ellipsis;
}
.main .main-header .main-tab .main-tab-item.active .main-tab-text-wrapper,
.main .main-header .main-tab .main-tab-item:hover .main-tab-text-wrapper,
.main .main-header .main-tab .main-tab-item.active .text,
.main .main-header .main-tab .main-tab-item:hover .text {
color: currentColor;
}
.main .main-header .main-tab .main-tab-item.active {
border-bottom-color: currentColor;
}
.main .main-header .main-tab .main-tab-item.active,
.main .main-header .main-tab .main-tab-item:hover {
color: var(--link-highlight-color);
}
.main .main-header .main-tab .main-tab-item .main-tab-text-wrapper {
color: var(--text-color);
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.main .main-icon {
width: 3.88rem;
margin-right: 1.88rem;
fill: var(--text-color);
display: inline-block;
vertical-align: middle;
}
.main .return-button {
position: absolute;
right: -4rem;
bottom: -24px;
color: var(--button-color);
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
@media (max-width: 768px) {
.main .return-button {
right: -3.4rem;
}
}
.main .return-button:hover {
color: var(--text-color);
}
.footer {
user-select: none;
}
.footer .section {
border-top: 1px solid var(--border-color);
padding: 1rem 0;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
font-family: "Noto Sans SC", sans-serif;
}
.footer .links {
flex-direction: row;
height: 2rem;
}
.footer .links .item {
padding: 0 1rem;
border-left: 2px solid var(--border-color);
height: inherit;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.footer .links .item:first-of-type {
border-left: none;
}
.footer .copyright {
flex-direction: column;
gap: 0.5rem;
font-size: 12px;
}

View File

@@ -0,0 +1,13 @@
.main {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
padding-bottom: 3rem;
margin: 0 auto;
width: 70%;
max-width: 100rem;
padding-top: 5rem;
min-height: calc(100vh - 5rem - 3rem);
}

View File

@@ -1,3 +1,14 @@
%outline-share {
content: "";
display: block;
position: absolute;
left: -3px;
height: 3px;
width: 100%;
border-left: var(--text-color) solid 3px;
border-right: var(--text-color) solid 3px;
}
.group { .group {
padding: 1rem; padding: 1rem;
display: flex; display: flex;
@@ -50,24 +61,13 @@
border: var(--home-item-outline-color) 1px dashed; border: var(--home-item-outline-color) 1px dashed;
padding: 6px; padding: 6px;
%share { &:before {
content: ""; @extend %outline-share;
display: block;
position: absolute;
left: -3px;
height: 3px;
width: 100%;
border-left: var(--text-color) solid 3px;
border-right: var(--text-color) solid 3px;
}
&::before {
@extend %share;
top: -3px; top: -3px;
} }
&::after { &:after {
@extend %share; @extend %outline-share;
bottom: -3px; bottom: -3px;
} }
} }

View File

@@ -1,4 +1,4 @@
@use 'tab_base'; @use '@/scss/_page_base.scss';
.group { .group {
flex-direction: column; flex-direction: column;

View File

@@ -0,0 +1,54 @@
@use '@/scss/_main_share.scss';
.error {
min-height: 100vh;
display: flex;
flex-direction: column;
font-size: 16px;
user-select: none;
.header {
padding: 1rem;
justify-content: space-between;
pointer-events: auto;
}
.main {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding-top: 10rem;
font-size: 3rem;
gap: 2rem;
}
.spine {
max-width: 600px;
flex: 1;
visibility: hidden;
opacity: 0;
&.active {
visibility: visible;
opacity: 1;
}
}
@media (max-width: 768px) {
.main {
padding-top: 6rem;
}
.content {
font-size: 2rem;
}
}
@media (max-width: 480px) {
.main {
padding-top: 4rem;
}
.content {
font-size: 1.5rem;
}
}
}

View File

@@ -0,0 +1 @@
@use '@/scss/_page_base.scss';

View File

@@ -0,0 +1,95 @@
@use '@/scss/_main_share.scss';
.main {
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
flex-wrap: wrap;
position: relative;
padding-right: 2rem;
.title {
font-size: 3rem;
font-weight: 700;
text-transform: uppercase;
line-height: 1.2em;
.icon {
width: 3.88rem;
margin-right: 1.88rem;
fill: var(--text-color);
display: inline-block;
vertical-align: middle;
}
@media (max-width: 600px) {
font-size: 2.5rem;
}
@media (max-width: 480px) {
font-size: 2rem;
}
}
.tab {
flex: auto;
white-space: pre;
user-select: none;
display: flex;
flex-direction: row;
justify-content: flex-end;
overflow: hidden;
z-index: 1;
.item {
font-size: 1.25rem;
line-height: 3em;
font-weight: 700;
padding: 0 1rem;
text-transform: uppercase;
cursor: pointer;
border-bottom: 0.3rem solid transparent;
display: inline-block;
cursor: pointer;
text-decoration: none;
overflow: hidden;
text-overflow: ellipsis;
.text-wrapper {
overflow: hidden;
text-overflow: ellipsis;
color: var(--text-color);
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
&.active,
&:hover {
color: var(--link-highlight-color);
.text-wrapper,
.text {
color: currentColor;
}
}
&.active {
border-bottom-color: currentColor;
}
}
}
}
.return-button {
position: absolute;
right: -4rem;
bottom: -24px;
color: var(--button-color);
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
&:hover {
color: var(--text-color);
}
@media (max-width: 768px) {
right: -3.4rem;
}
}
}

View File

@@ -0,0 +1,54 @@
.drawer {
position: fixed;
top: 0;
left: -15rem;
width: 15rem;
height: 100%;
z-index: 2;
pointer-events: none;
transition: left cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
text-align: center;
display: flex;
flex-direction: row;
align-items: flex-start;
user-select: none;
.links {
padding: 8rem 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
background-color: var(--drawer-background-color);
height: 100%;
width: 15rem;
.link {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.2rem;
color: var(--text-color);
font-size: 0.8rem;
font-weight: 500;
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
text-transform: uppercase;
&:hover,
&.active {
color: var(--link-highlight-color);
}
}
}
.overlay {
height: 100%;
flex-grow: 1;
z-index: 2;
}
&.active {
pointer-events: all;
left: 0;
width: 100vw;
}
}

View File

@@ -0,0 +1,35 @@
.footer {
user-select: none;
.section {
border-top: 1px solid var(--border-color);
padding: 1rem 0;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
font-family: "Noto Sans SC", sans-serif;
}
.links {
flex-direction: row;
height: 2rem;
.item {
padding: 0 1rem;
border-left: 2px solid var(--border-color);
height: inherit;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex-wrap: wrap;
&:first-of-type {
border-left: none;
}
}
}
.copyright {
flex-direction: column;
gap: 0.5rem;
font-size: 12px;
}
}

View File

@@ -0,0 +1,74 @@
.header {
width: auto;
position: fixed;
left: 0;
top: 0;
right: 0;
padding: 1rem;
z-index: 3;
height: 3rem;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
justify-content: flex-start;
pointer-events: none;
.spacer {
flex-grow: 1;
}
.dropdown {
margin-left: auto;
}
.extra-area {
display: flex;
flex-direction: row;
align-items: center;
pointer-events: auto;
}
}
.navButton {
padding: 0.5rem;
font-size: 2rem;
width: 2rem;
height: 2rem;
cursor: pointer;
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column;
z-index: 2;
pointer-events: auto;
.bar {
width: 2rem;
height: 0.2rem;
background-color: var(--text-color);
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
&:nth-child(1) {
transform: translate(0, -200%);
}
&:nth-child(3) {
transform: translate(0, 200%);
}
}
&.active {
.bar {
&:nth-child(1) {
transform: translate(0, 100%) rotateZ(45deg) scaleX(0.5) translate(-50%);
}
&:nth-child(2) {
transform: rotateZ(-45deg);
}
&:nth-child(3) {
transform: translate(0, -100%) rotateZ(45deg) scaleX(0.5) translate(50%);
}
}
}
}

View File

@@ -1,9 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["src/*"] "@/*": ["src/*"]
} }
}, },
"exclude": ["node_modules", "**/node_modules", "dist", "operator", "release"] "exclude": ["node_modules", "**/node_modules", "dist", "operator", "release"]
} }

View File

@@ -54,6 +54,32 @@ export default function ({ backgrounds, charwordTable }) {
}) })
}) })
writeSync((new EnvGenerator()).generate([
{
key: "app_title",
value: __config.directory.title
}, {
key: "app_voice_url",
value: __config.directory.voice
}, {
key: "voice_folders",
value: JSON.stringify(__config.folder.voice)
}, {
key: "directory_folder",
value: JSON.stringify(__config.folder.directory)
}
, {
key: "background_folder",
value: JSON.stringify(__config.folder.background)
}, {
key: "available_operators",
value: JSON.stringify(Object.keys(__config.operators))
}, {
key: "error_files",
value: JSON.stringify(__config.directory.error).replace('#', '%23')
}
]), path.join(__projetRoot, 'directory', '.env'))
writeSync(JSON.stringify(directoryJson, null), path.join(targetFolder, "directory.json")) writeSync(JSON.stringify(directoryJson, null), path.join(targetFolder, "directory.json"))
writeSync(JSON.stringify(versionJson, null), path.join(targetFolder, "version.json")) writeSync(JSON.stringify(versionJson, null), path.join(targetFolder, "version.json"))
writeSync(JSON.stringify(changelogsArray, null), path.join(targetFolder, "changelogs.json")) writeSync(JSON.stringify(changelogsArray, null), path.join(targetFolder, "changelogs.json"))

View File

@@ -148,31 +148,6 @@ class ViteRunner {
global.__config = this.#globalConfig global.__config = this.#globalConfig
} }
const directoryDir = path.resolve(__projetRoot, 'directory') const directoryDir = path.resolve(__projetRoot, 'directory')
writeSync((new EnvGenerator()).generate([
{
key: "app_title",
value: this.#globalConfig.directory.title
}, {
key: "app_voice_url",
value: this.#globalConfig.directory.voice
}, {
key: "voice_folders",
value: JSON.stringify(this.#globalConfig.folder.voice)
}, {
key: "directory_folder",
value: JSON.stringify(this.#globalConfig.folder.directory)
}
, {
key: "background_folder",
value: JSON.stringify(this.#globalConfig.folder.background)
}, {
key: "available_operators",
value: JSON.stringify(Object.keys(this.#globalConfig.operators))
}, {
key: "error_files",
value: JSON.stringify(this.#globalConfig.directory.error).replace('#', '%23')
}
]), path.join(directoryDir, '.env'))
this.#mode = process.argv[3] this.#mode = process.argv[3]
const publicDir = path.resolve(__projetRoot, this.#globalConfig.folder.release) const publicDir = path.resolve(__projetRoot, this.#globalConfig.folder.release)
const assetsDir = '_directory' const assetsDir = '_directory'