feat(directory): finish home page

This commit is contained in:
Haoyu Xu
2023-02-26 01:06:10 -05:00
parent b5f84c9380
commit 81ee2d2170
24 changed files with 652 additions and 214 deletions

View File

@@ -1,13 +1,20 @@
@import url('https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@400;500&family=Noto+Sans+SC&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap');
@import url('https://fonts.cdnfonts.com/css/bender');
@import url('https://fonts.cdnfonts.com/css/geometos');
:root { :root {
--text-color: rgba(255, 255, 255, 0.87); --text-color: rgba(255, 255, 255, 0.87);
--date-color: rgba(255, 255, 255, 0.2);
--border-color: #707070; --border-color: #707070;
--link-highlight-color: #33b5e5; --link-highlight-color: #33b5e5;
--drawer-background-color: rgba(0, 0, 0, 0.5); --drawer-background-color: rgba(0, 0, 0, 0.88);
--root-background-color: #131313; --root-background-color: #131313;
--home-item-hover-background-color: rgba(67, 67, 67, 0.3);
--home-item-background-linear-gradient-color: rgba(255, 255, 255, 0.1);
--home-item-outline-color: rgba(214, 214, 214, 0.3);
--button-color: #666;
font-family: "Josefin Sans", "Noto Sans SC", sans-serif; font-family: "Geometos", "Noto Sans SC", sans-serif;
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
font-weight: 400; font-weight: 400;

View File

@@ -11,8 +11,9 @@ import ErrorPage from "@/routes/error-page";
import routes from "@/routes"; import routes from "@/routes";
import '@/App.css'; import '@/App.css';
import 'reset-css'; import 'reset-css';
import { TitleProvider } from '@/context/useTitleContext';
import { LanguageProvider } from '@/context/useLanguageContext'; import { LanguageProvider } from '@/context/useLanguageContext';
import { ConfigProvider } from '@/context/useConfigContext';
import { HeaderProvider } from '@/context/useHeaderContext';
const router = createBrowserRouter( const router = createBrowserRouter(
createRoutesFromElements( createRoutesFromElements(
@@ -40,10 +41,12 @@ const router = createBrowserRouter(
ReactDOM.createRoot(document.getElementById('root')).render( ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode> <React.StrictMode>
<LanguageProvider> <ConfigProvider>
<TitleProvider> <LanguageProvider>
<RouterProvider router={router} /> <HeaderProvider>
</TitleProvider> <RouterProvider router={router} />
</LanguageProvider> </HeaderProvider>
</LanguageProvider>
</ConfigProvider>
</React.StrictMode> </React.StrictMode>
) )

View File

@@ -0,0 +1,13 @@
export default function CharIcon(props) {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox={props.viewBox}>
{
props.type === 'operator' ?
<g><path d="M89 17.5 30.4 57 24.3 71.4 82.9 32.6Z"></path><path d="M0 17.5 58.6 57 64.7 71.4 6.1 32.7Z"> </path><path d="M89 0 30.4 39.5 24.3 53.9 82.9 15.1Z"> </path><path d="M0 0 58.6 39.5 64.7 53.9 6.1 15.2Z"> </path></g>
:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 94.563 67.437"><path d="M90.4 50.6l-39.8-23.5v-4c0-4.5-5-6.5-5-6.5a5.4 5.4 0 012.2-10.1c2.7 0 5.3 1.5 5.5 4.8.4 5.3 6.4 3.9 6.4-.3a11.7 11.7 0 00-12-11c-9 0-11.6 8.8-11.6 11.6a11.5 11.5 0 001.6 6.2c2.2 3.8 6.6 4.3 6.6 6.8v2.5L4.2 50.7c-4 2.3-4.7 7.3-3.8 10.3a9.1 9.1 0 009.1 6.4h75.2c5.9 0 8.6-3.4 9.5-6.3C95 58.1 95 53.4 90.4 50.6Zm-5.6 10.3h-75.2c-2.4.1-4-3.3-1.5-4.8l39.2-22.9 39 22.8A2.7 2.7 0 0184.7 60.8Z" /></svg>
}
</svg>
)
}

View File

@@ -37,7 +37,7 @@
.dropdown .menu { .dropdown .menu {
opacity: 0; opacity: 0;
position: absolute; position: absolute;
background-color: var(--background-color); background-color: var(--root-background-color);
color: var(--text-color); color: var(--text-color);
width: max-content; width: max-content;
z-index: -1; z-index: -1;

View File

@@ -28,7 +28,10 @@ export default function Dropdown(props) {
<li <li
key={item.name} key={item.name}
className={`item${item.name === props.text ? ' active' : ''}`} className={`item${item.name === props.text ? ' active' : ''}`}
onClick={() => props.onClick(item)} onClick={() => {
props.onClick(item)
toggleDropdown()
}}
> >
{item.name} {item.name}
</li> </li>

View File

@@ -0,0 +1,23 @@
.main-border {
position: relative;
bottom: 1px;
border-bottom: 1px solid var(--text-color);
}
.main-border::before, .main-border::after {
content: "";
display: block;
position: absolute;
width: 5px;
height: 5px;
top: -2px;
background-color: var(--text-color);
}
.main-border::before {
right: 100%;
}
.main-border::after {
left: 100%;
}

View File

@@ -0,0 +1,7 @@
import './main_border.css';
export default function MainBorder(props) {
return (
<section className="main-border"/>
)
}

View File

@@ -41,13 +41,18 @@
.popup .title { .popup .title {
font-size: 3rem; font-size: 3rem;
font-weight: 700; font-weight: 700;
margin-bottom: 1rem;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-content: center; align-content: center;
align-items: center; align-items: center;
padding-bottom: 1rem;
}
.popup .content {
line-height: 1.5rem;
padding: 1rem;
} }
.popup .overlay { .popup .overlay {
@@ -66,3 +71,12 @@
opacity: 0.5; opacity: 0.5;
visibility: visible; visibility: visible;
} }
.popup .return-button {
color: #666;
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.popup .return-button:hover {
color: var(--text-color);
}

View File

@@ -18,7 +18,7 @@ export default function Popup(props) {
<section className='wrapper'> <section className='wrapper'>
<section className='title'> <section className='title'>
<span>{props.title}</span> <span>{props.title}</span>
<ReturnButton onClick={toggle} /> <ReturnButton onClick={toggle} className="return-button"/>
</section> </section>
<section className='content'> <section className='content'>
{props.children} {props.children}

View File

@@ -17,20 +17,20 @@
.return-button .bar { .return-button .bar {
width: 1rem; width: 1rem;
height: 0.4rem; height: 0.4rem;
background-color: var(--text-color); background-color: currentColor;
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
} }
.return-button .bar-arrow-left { .return-button .bar-arrow-left {
border-top: 0.24rem solid transparent; border-top: 0.24rem solid transparent;
border-bottom: 0.24rem solid transparent; border-bottom: 0.24rem solid transparent;
border-right: 0.3rem solid var(--text-color); border-right: 0.3rem solid currentColor;
} }
.return-button .bar-arrow-right { .return-button .bar-arrow-right {
border-top: 0.24rem solid transparent; border-top: 0.24rem solid transparent;
border-bottom: 0.24rem solid transparent; border-bottom: 0.24rem solid transparent;
border-left: 0.3rem solid var(--text-color); border-left: 0.3rem solid currentColor;
} }
.return-button .bar-wrapper:nth-child(1) { .return-button .bar-wrapper:nth-child(1) {

View File

@@ -0,0 +1,19 @@
import { createContext, useState, useEffect } from "react"
export const ConfigContext = createContext()
export function ConfigProvider(props) {
const [config, setConfig] = useState(null)
useEffect(() => {
fetch("/_assets/directory.json").then(res => res.json()).then(data => {
setConfig(data)
})
}, [])
return (
<ConfigContext.Provider value={{ config }}>
{props.children}
</ConfigContext.Provider>
)
}

View File

@@ -0,0 +1,39 @@
import {
createContext,
useState,
useEffect,
useContext
} from "react"
import { LanguageContext } from "@/context/useLanguageContext"
export const HeaderContext = createContext()
export function HeaderProvider(props) {
const [key, setTitle] = useState('')
const [title, setRealTitle] = useState('')
const {
language,
i18n
} = useContext(LanguageContext)
const [tabs, setTabs] = useState(null)
const [currentTab, setCurrentTab] = useState(null)
useEffect(() => {
let newTitle = key
if (i18n.key[newTitle]) {
newTitle = i18n.key[newTitle][language]
}
document.title = `${newTitle} - ${import.meta.env.VITE_APP_TITLE}`;
setRealTitle(newTitle)
}, [key, language])
return (
<HeaderContext.Provider value={{
title, setTitle,
tabs, setTabs,
currentTab, setCurrentTab,
}}>
{props.children}
</HeaderContext.Provider>
)
}

View File

@@ -4,20 +4,20 @@ import i18n from '@/i18n'
export const LanguageContext = createContext() export const LanguageContext = createContext()
export function LanguageProvider(props) { export function LanguageProvider(props) {
const drawerTextDefaultLang = "en-US" const textDefaultLang = "en-US"
const [language, setLanguage] = useState(i18n.available.includes(navigator.language) ? navigator.language : "en-US") // language will be default to en-US if not available const [language, setLanguage] = useState(i18n.available.includes(navigator.language) ? navigator.language : "en-US") // language will be default to en-US if not available
const [drawerAlternateLang, setDrawerAlternateLang] = useState(null) // drawerAlternateLang will be default to zh-CN if language is en-* const [alternateLang, setAlternateLang] = useState(null) // drawerAlternateLang will be default to zh-CN if language is en-*
useEffect(() => { useEffect(() => {
setDrawerAlternateLang(language.startsWith("en") ? "zh-CN" : language) setAlternateLang(language.startsWith("en") ? "zh-CN" : language)
}, [language]) }, [language])
return ( return (
<LanguageContext.Provider <LanguageContext.Provider
value={{ value={{
language, setLanguage, language, setLanguage,
drawerTextDefaultLang, textDefaultLang,
drawerAlternateLang, alternateLang,
i18n i18n
}} }}
> >

View File

@@ -1,25 +0,0 @@
import { createContext, useState, useEffect, useContext } from "react"
import { LanguageContext } from '@/context/useLanguageContext';
export const TitleContext = createContext()
export function TitleProvider(props) {
const { i18n, language } = useContext(LanguageContext)
const [fakeTitle, setTitle] = useState('dynamic_compile')
const [title, setActualTitle] = useState(null)
useEffect(() => {
let newTitle = fakeTitle
if (i18n.key[fakeTitle]) {
newTitle = i18n.key[fakeTitle][language]
}
document.title = `${newTitle} - ${import.meta.env.VITE_APP_TITLE}`;
setActualTitle(newTitle)
}, [fakeTitle, language])
return (
<TitleContext.Provider value={{ title, setTitle }}>
{props.children}
</TitleContext.Provider>
)
}

View File

@@ -11,14 +11,22 @@
"zh-CN": "首页", "zh-CN": "首页",
"en-US": "Home" "en-US": "Home"
}, },
"news": { "changelogs": {
"zh-CN": "新闻", "zh-CN": "更新日志",
"en-US": "News" "en-US": "Changelogs"
}, },
"offical_page": { "offical_page": {
"zh-CN": "官方页面", "zh-CN": "官方页面",
"en-US": "Offical Page" "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": { "privacy_policy": {
"zh-CN": "隐私政策", "zh-CN": "隐私政策",
"en-US": "Privacy Policy" "en-US": "Privacy Policy"
@@ -27,6 +35,18 @@
"zh-CN": "联系我们", "zh-CN": "联系我们",
"en-US": "Contact Us" "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": {
"zh-CN": "简体中文", "zh-CN": "简体中文",
"en-US": "Chinese (Simplified)" "en-US": "Chinese (Simplified)"

View File

@@ -1,6 +1,6 @@
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 News from "@/routes/path/news"; import Changelogs from "@/routes/path/changelogs";
export default [ export default [
{ {
@@ -16,10 +16,10 @@ export default [
element: <Operator />, element: <Operator />,
inDrawer: false inDrawer: false
}, { }, {
path: "news", path: "changelogs",
index: false, index: false,
name: "news", name: "changelogs",
element: <News />, element: <Changelogs />,
inDrawer: true inDrawer: true
}, { }, {
path: "https://ak.hypergryph.com/archive/dynamicCompile/", path: "https://ak.hypergryph.com/archive/dynamicCompile/",

View File

@@ -0,0 +1,32 @@
import {
useState,
useEffect,
useContext
} from 'react'
import './changelogs.css'
import { HeaderContext } from '@/context/useHeaderContext';
import { LanguageContext } from '@/context/useLanguageContext';
import useUmami from '@parcellab/react-use-umami'
export default function Changelogs(props) {
const _trackEvt = useUmami('/changelogs')
const {
setTitle,
setTabs,
currentTab, setCurrentTab
} = useContext(HeaderContext)
const { language, i18n } = useContext(LanguageContext)
useEffect(() => {
setTitle('changelogs')
setTabs([])
}, [])
return (
<section>
<section>
Under Construction :(
</section>
</section>
)
}

View File

@@ -0,0 +1,126 @@
.home .item-group {
padding: 1rem;
display: flex;
align-items: flex-end;
flex-wrap: wrap;
}
.home .item-group-date {
margin: 1.5rem;
font-family: "Bender";
font-weight: bold;
text-align: right;
color: var(--date-color);
font-size: 1.5rem;
letter-spacing: 0.1rem;
flex: auto;
}
.home .item-group .item {
position: relative;
cursor: pointer;
width: 180px;
margin: 1.25rem;
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 5px);
}
.home .item-group .item .item-outline {
content: "";
display: block;
position: absolute;
opacity: 0;
visibility: hidden;
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.home .item-group .item .item-outline{
width: 100%;
height: 100%;
left: -6px;
top: -6px;
border: var(--home-item-outline-color) 1px dashed;
padding: 6px;
}
.home .item-group .item .item-outline::before,
.home .item-group .item .item-outline::after {
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;
}
.home .item-group .item .item-outline::before {
top: -3px;
}
.home .item-group .item .item-outline::after {
bottom: -3px;
}
.home .item-group .item:hover .item-outline,
.home .item-group .item:hover .item-info .item-info-background {
opacity: 1;
visibility: visible;
}
.home .item-group .item .item-img {
height: 360px;
width: 100%;
transition: background-color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.home .item-group .item:hover .item-img {
background-color: var(--home-item-hover-background-color);
}
.home .item-group .item .item-info {
white-space: nowrap;
position: relative;
padding: 0.8rem 0.4rem;
line-height: 1;
}
.home .item-group .item .item-info .item-title-container {
color: white;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.25rem;
font-weight: bold;
}
.home .item-group .item .item-info .item-title-container .item-title,
.home .item-group .item .item-info .item-text-wrapper {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.home .item-group .item .item-info .item-title-container .item-type {
width: 1.5rem;
fill: var(--text-color)
}
.home .item-group .item .item-info .item-text {
color: var(--text-color);
font-size: 0.75rem;
font-family: "Geometos";
margin-top: 1rem;
}
.home .item-group .item .item-info .item-info-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
visibility: hidden;
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
background-image: linear-gradient(70deg, transparent 40%, currentColor 150%);
}

View File

@@ -1,32 +1,115 @@
import { import {
useState, useState,
useEffect, useEffect,
useContext useContext,
useMemo
} from 'react' } from 'react'
import {
Link,
} from "react-router-dom";
import './home.css' import './home.css'
import { TitleContext } from '@/context/useTitleContext'; import { ConfigContext } from '@/context/useConfigContext';
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'
export default function Home(props) { export default function Home() {
const { title, setTitle } = useContext(TitleContext) const _trackEvt = useUmami('/')
useEffect(() => { const {
fetch("/_assets/directory.json").then(res => res.json()).then(data => { setTitle,
console.log(data) setTabs,
}) currentTab, setCurrentTab } = useContext(HeaderContext)
}, []) const { config } = useContext(ConfigContext)
const {
language,
textDefaultLang,
alternateLang,
i18n
} = useContext(LanguageContext)
useEffect(() => { useEffect(() => {
setTitle('dynamic_compile') setTitle('dynamic_compile')
setTabs([
{
key: 'all',
text: i18n.key.all
}, {
key: 'operator',
text: i18n.key.elite2
}, {
key: 'skin',
text: i18n.key.skin
}
])
setCurrentTab('all')
}, []) }, [])
const renderContent = () => {
const list = config?.filter((v) => currentTab === 'all' || currentTab === v.type)
const content = []
if (list) {
console.log(list)
let items = {}
list.forEach(element => {
if (items[element.date]) {
items[element.date].push(toItemEl(element))
} else {
items[element.date] = [toItemEl(element)]
}
});
items = Object.keys(items).sort().reverse().reduce((r, k) => {
r[k] = items[k]
return r
}, {})
for (const [date, group] of Object.entries(items)) {
content.push(
<section className="item-group-wrapper" key={date}>
<section className="item-group">
{group.map((v) => v)}
<section className='item-group-date'>{date}</section>
</section>
<MainBorder />
</section>
)
}
}
return content
}
const toItemEl = (item) => {
return (
<Link reloadDocument to={`/${item.link}`} className="item" key={item.link}>
<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'
} />
</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>
</Link>
)
}
const content = useMemo(() => renderContent(), [currentTab, config, language])
return ( return (
<section> <section className="home">
<section> {content}
{title}
<button onClick={() => setTitle('123')}>123</button>
</section>
<section>
22s
</section>
</section> </section>
) )
} }

View File

@@ -1,27 +0,0 @@
import {
useState,
useEffect,
useContext
} from 'react'
import './news.css'
import { TitleContext } from '@/context/useTitleContext';
export default function News(props) {
const { title, setTitle } = useContext(TitleContext)
useEffect(() => {
setTitle('news')
}, [])
return (
<section>
<section>
1
<button onClick={() => setTitle('123')}>123</button>
</section>
<section>
2
</section>
</section>
)
}

View File

@@ -4,13 +4,15 @@ import {
useContext useContext
} from 'react' } from 'react'
import './operator.css' import './operator.css'
import { TitleContext } from '@/context/useTitleContext'; import { HeaderContext } from '@/context/useHeaderContext';
import useUmami from '@parcellab/react-use-umami'
export default function Operator(props) { export default function Operator(props) {
const { title, setTitle } = useContext(TitleContext) const _trackEvt = useUmami('/operator/:key')
const { setTitle } = useContext(HeaderContext)
useEffect(() => { useEffect(() => {
setTitle('Chen') setTitle('chen')
}, []) }, [])
return ( return (

View File

@@ -1,149 +1,204 @@
.main { .main {
flex-grow: 1; 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;
} }
.header { .header {
width: auto; width: auto;
position: sticky; position: sticky;
top: 0; top: 0;
right: 0; right: 0;
padding: 1rem; padding: 1rem;
z-index: 1; z-index: 2;
height: 3rem; height: 3rem;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: flex-start; justify-content: flex-start;
} }
.header .dropdown { .header .dropdown {
margin-left: auto; margin-left: auto;
} }
.navButton { .navButton {
padding: 0.5rem; padding: 0.5rem;
font-size: 2rem; font-size: 2rem;
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
flex-direction: column; flex-direction: column;
z-index: 2; z-index: 2;
} }
.navButton .bar { .navButton .bar {
width: 2rem; width: 2rem;
height: 0.2rem; height: 0.2rem;
background-color: var(--text-color); background-color: var(--text-color);
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
} }
.navButton .bar:nth-child(1) { .navButton .bar:nth-child(1) {
transform: translate(0, -200%); transform: translate(0, -200%);
} }
.navButton .bar:nth-child(3) { .navButton .bar:nth-child(3) {
transform: translate(0, 200%); transform: translate(0, 200%);
} }
.navButton.active .bar:nth-child(1) { .navButton.active .bar:nth-child(1) {
transform: translate(0, 100%) rotateZ(45deg) scaleX(0.5) translate(-50%); transform: translate(0, 100%) rotateZ(45deg) scaleX(0.5) translate(-50%);
} }
.navButton.active .bar:nth-child(2) { .navButton.active .bar:nth-child(2) {
transform: rotateZ(-45deg); transform: rotateZ(-45deg);
} }
.navButton.active .bar:nth-child(3) { .navButton.active .bar:nth-child(3) {
transform: translate(0, -100%) rotateZ(45deg) scaleX(0.5) translate(50%); transform: translate(0, -100%) rotateZ(45deg) scaleX(0.5) translate(50%);
} }
.drawer { .drawer {
position: fixed; position: fixed;
top: 0; top: 0;
left: -15rem; left: -15rem;
width: 15rem; width: 15rem;
height: 100%; height: 100%;
z-index: 0; z-index: 1;
pointer-events: none; pointer-events: none;
transition: left cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; transition: left cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
text-align: center; text-align: center;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-start; align-items: flex-start;
gap: 1rem;
} }
.drawer .links { .drawer .links {
padding: 8rem 0; padding: 8rem 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
background-color: var(--drawer-background-color); background-color: var(--drawer-background-color);
height: 100%; height: 100%;
width: 15rem; width: 15rem;
} }
.drawer .overlay { .drawer .overlay {
height: 100%; height: 100%;
flex-grow: 1; flex-grow: 1;
z-index: 2; z-index: 2;
} }
.drawer.active { .drawer.active {
pointer-events: all; pointer-events: all;
left: 0; left: 0;
width: 100vw; width: 100vw;
} }
.drawer a { .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;
}
.main .main-header .main-title {
font-size: 3rem;
font-weight: 700;
text-transform: uppercase;
}
.main .main-header .main-tab {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
align-items: center; align-items: center;
gap: 0.2rem; }
color: var(--text-color);
font-size: 0.8rem; .main .main-header .main-tab .main-tab-item {
font-weight: 500; font-size: 1em;
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; line-height: 3rem;
font-weight: 700;
padding: 0 1rem;
text-transform: uppercase; text-transform: uppercase;
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
cursor: pointer;
border-bottom: 0.3rem solid transparent;
} }
.drawer a:hover { .main .main-header .main-tab .main-tab-item.active {
color: var(--link-highlight-color); color: var(--link-highlight-color);
border-bottom-color: var(--link-highlight-color);
} }
.main { .main .main-header .return-button {
margin: 0 auto; position: absolute;
width: 90%; right: -4rem;
max-width: 100rem; bottom: -1.5rem;
color: #666;
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.main .main-header .return-button:hover {
color: var(--text-color);
} }
.footer .section { .footer .section {
border-top: 1px solid var(--border-color); border-top: 1px solid var(--border-color);
padding: 1rem 0; padding: 1rem 0;
display: flex; display: flex;
align-content: center; align-content: center;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-wrap: nowrap; flex-wrap: nowrap;
font-family: "Noto Sans SC", sans-serif;
} }
.footer .links { .footer .links {
flex-direction: row; flex-direction: row;
gap: 1rem; gap: 1rem;
} }
.footer .links .separator { .footer .links .separator {
height: 1rem; height: 1rem;
border-left: 2px solid var(--border-color); border-left: 2px solid var(--border-color);
} }
.footer .copyright { .footer .copyright {
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
font-size: 12px; font-size: 12px;
} }

View File

@@ -7,28 +7,47 @@ import {
Outlet, Outlet,
Link, Link,
NavLink, NavLink,
useNavigation,
useLocation,
useNavigate, useNavigate,
} from "react-router-dom"; } from "react-router-dom";
import './root.css' import './root.css'
import routes from '@/routes' import routes from '@/routes'
import { TitleContext } from '@/context/useTitleContext'; import { HeaderContext } from '@/context/useHeaderContext';
import { LanguageContext } from '@/context/useLanguageContext'; import { LanguageContext } from '@/context/useLanguageContext';
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';
export default function Root(props) { export default function Root(props) {
const { title, setTitle } = useContext(TitleContext) const navigate = useNavigate()
const [drawerHidden, setDrawerHidden] = useState(true) const [drawerHidden, setDrawerHidden] = useState(true)
const { const {
language, setLanguage, language, setLanguage,
drawerTextDefaultLang, textDefaultLang,
drawerAlternateLang, alternateLang,
i18n i18n
} = useContext(LanguageContext) } = useContext(LanguageContext)
const { title, tabs, currentTab, setCurrentTab } = useContext(HeaderContext)
const [drawerDestinations, setDrawerDestinations] = useState(null) const [drawerDestinations, setDrawerDestinations] = useState(null)
const currentYear = new Date().getFullYear()
const [headerTabs, setHeaderTabs] = useState(null)
const renderHeaderTabs = (tabs) => {
setHeaderTabs(tabs?.map((item) => {
return (
<section
key={item.key}
className={`main-tab-item ${currentTab === item.key ? 'active' : ''}`}
onClick={(e) => {
setCurrentTab(item.key)
item.onClick && item.onClick(e, item.key)
}}
>
{item.text[language]}
</section>
)
}))
}
const toggleDrawer = () => { const toggleDrawer = () => {
setDrawerHidden(!drawerHidden) setDrawerHidden(!drawerHidden)
@@ -38,25 +57,34 @@ export default function Root(props) {
return routes.filter((item) => item.inDrawer).map((item) => { return routes.filter((item) => item.inDrawer).map((item) => {
if (typeof item.element.type === 'string') { if (typeof item.element.type === 'string') {
return ( return (
<a key={item.name} <Link reloadDocument
href={item.path} key={item.name}
target="_blank"> to={item.path}
target="_blank"
className="link"
onClick={() => toggleDrawer()}
>
<section> <section>
{i18n.key[item.name][drawerTextDefaultLang]} {i18n.key[item.name][textDefaultLang]}
</section> </section>
<section> <section>
{i18n.key[item.name][drawerAlternateLang]} {i18n.key[item.name][alternateLang]}
</section> </section>
</a> </Link>
) )
} else { } else {
return ( return (
<NavLink to={item.path} key={item.name}> <NavLink
to={item.path}
key={item.name}
className="link"
onClick={() => toggleDrawer()}
>
<section> <section>
{i18n.key[item.name][drawerTextDefaultLang]} {i18n.key[item.name][textDefaultLang]}
</section> </section>
<section> <section>
{i18n.key[item.name][drawerAlternateLang]} {i18n.key[item.name][alternateLang]}
</section> </section>
</NavLink> </NavLink>
) )
@@ -66,7 +94,11 @@ export default function Root(props) {
useEffect(() => { useEffect(() => {
setDrawerDestinations(renderDrawerDestinations()) setDrawerDestinations(renderDrawerDestinations())
}, [drawerAlternateLang]) }, [alternateLang])
useEffect(() => {
renderHeaderTabs(tabs)
}, [tabs, currentTab, language])
return ( return (
<> <>
@@ -104,21 +136,33 @@ export default function Root(props) {
/> />
</nav> </nav>
<main className='main'> <main className='main'>
<section> <section className='main-header'>
<section>{title}</section> <section className='main-title'>{title}</section>
<section> <section className='main-tab'>
{/* <NavLink to="/home">All</NavLink> {headerTabs}
<NavLink to="/about">Operator</NavLink>
<NavLink to="/contact">Skill</NavLink> */}
</section> </section>
<ReturnButton
className='return-button'
onClick={() => {
navigate("/")
}}
/>
</section> </section>
<MainBorder />
<Outlet /> <Outlet />
</main> </main>
<footer className='footer'> <footer className='footer'>
<section className='links section'> <section className='links section'>
<a href="https://privacy.halyul.dev" target="_blank" className='item'>{i18n.key.privacy_policy[language]}</a> <Popup
className='item'
title={i18n.key.disclaimer[language]}
>
{i18n.key.disclaimer_content[language]}
</Popup>
<span className='separator' /> <span className='separator' />
<a href="https://github.com/Halyul/aklive2d" target="_blank" className='item'>Github</a> <Link reloadDocument to="https://privacy.halyul.dev" target="_blank" className='item'>{i18n.key.privacy_policy[language]}</Link>
<span className='separator' />
<Link reloadDocument to="https://github.com/Halyul/aklive2d" target="_blank" className='item'>Github</Link>
<span className='separator'/> <span className='separator'/>
<Popup <Popup
className='item' className='item'
@@ -128,9 +172,9 @@ export default function Root(props) {
</Popup> </Popup>
</section> </section>
<section className='copyright section'> <section className='copyright section'>
<span>Spine Runtimes © 2013 - 2019, Esoteric Software LLC</span> <span>Spine Runtimes © 2013 - 2019 Esoteric Software LLC</span>
<span>Assets © 2017 - 2023 上海鹰角网络科技有限公司</span> <span>Assets © 2017 - {currentYear} Arknights/Hypergryph Co., Ltd</span>
<span>Source Code © 2021 - 2023 Halyul</span> <span>Source Code © 2021 - {currentYear} Halyul</span>
</section> </section>
</footer> </footer>
</> </>