feat(directory): added animated cursor

This commit is contained in:
Haoyu Xu
2023-10-23 16:47:09 -04:00
parent f361049de0
commit 67daf20aa7
22 changed files with 100 additions and 29 deletions

View File

@@ -15,6 +15,7 @@
--home-item-background-linear-gradient-color: rgba(0, 0, 0, 10%); --home-item-background-linear-gradient-color: rgba(0, 0, 0, 10%);
--home-item-outline-color: rgba(41, 41, 41, 30%); --home-item-outline-color: rgba(41, 41, 41, 30%);
--button-color: #999; --button-color: #999;
--cursor-color: var(--text-color);
} }
@mixin dark-theme() { @mixin dark-theme() {
@@ -30,6 +31,7 @@
--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;
--cursor-color: var(--text-color);
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {

View File

@@ -1,5 +1,6 @@
import React, { import React, {
useState, useState,
useCallback
} from 'react' } from 'react'
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classes from './scss/dropdown.module.scss' import classes from './scss/dropdown.module.scss'
@@ -11,9 +12,17 @@ export default function Dropdown(props) {
setHidden(!hidden) setHidden(!hidden)
} }
const onMouseEnter = useCallback((item) => {
document.documentElement.style.setProperty('--cursor-color', item.color);
}, [])
const onMouseLeave = useCallback(() => {
document.documentElement.style.setProperty('--cursor-color', 'var(--text-color)');
}, [])
return ( return (
<> <>
<section className={`${classes.dropdown} ${hidden ? '' : classes.active} ${props.className ? props.className : ''} ${props.left ? classes.left : ''}`}> <section className={`${classes.dropdown} ${hidden ? '' : classes.active} ${props.className ? props.className : ''} ${props.left ? classes.left : ''}`} data-type="clickable">
<section <section
className={classes.text} className={classes.text}
onClick={() => toggleDropdown()} onClick={() => toggleDropdown()}
@@ -56,6 +65,9 @@ export default function Dropdown(props) {
toggleDropdown() toggleDropdown()
}} }}
style={item.color ? { color: item.color } : {}} style={item.color ? { color: item.color } : {}}
data-type="clickable"
onMouseEnter={() => onMouseEnter(item)}
onMouseLeave={() => onMouseLeave()}
> >
{ {
item.icon ? ( item.icon ? (

View File

@@ -26,7 +26,7 @@ export default function Popup(props) {
</section> </section>
</section> </section>
<section className={`${classes.overlay} ${hidden ? '' : classes.active}`} <section className={`${classes.overlay} ${hidden ? '' : classes.active}`}
onClick={() => toggle()} /> onClick={() => toggle()} data-type="clickable"/>
</section> </section>
<span <span
className={classes['entry-text']} className={classes['entry-text']}

View File

@@ -8,6 +8,7 @@ export default function ReturnButton(props) {
<> <>
<section className={`${classes['return-button']} ${props.className ? props.className : ''}`} <section className={`${classes['return-button']} ${props.className ? props.className : ''}`}
onClick={() => props.onClick()} onClick={() => props.onClick()}
data-type="clickable"
> >
<section className={classes.wrapper}> <section className={classes.wrapper}>
<section className={classes["arrow-left"]}></section> <section className={classes["arrow-left"]}></section>

View File

@@ -4,7 +4,6 @@
user-select: none; user-select: none;
z-index: 2; z-index: 2;
padding: 0.5em; padding: 0.5em;
cursor: pointer;
&.left { &.left {
.popup, .menu { .popup, .menu {
@@ -37,7 +36,6 @@
z-index: -1; z-index: -1;
top: 2em; top: 2em;
right: 0; right: 0;
cursor: auto;
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
overflow: auto; overflow: auto;
padding: 0.5rem; padding: 0.5rem;
@@ -92,7 +90,6 @@
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
visibility: hidden; visibility: hidden;
color: var(--link-highlight-color); color: var(--link-highlight-color);
cursor: auto;
&.left { &.left {
left: 0; left: 0;
@@ -120,7 +117,6 @@
} }
.item { .item {
cursor: pointer;
padding: 0.5rem; padding: 0.5rem;
font-size: 1rem; font-size: 1rem;
display: flex; display: flex;
@@ -161,7 +157,6 @@
bottom: 0; bottom: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
cursor: auto;
} }
&.active, &.active,

View File

@@ -1,7 +1,3 @@
.entry-text {
cursor: pointer;
}
.popup { .popup {
position: fixed; position: fixed;
left: 0; left: 0;

View File

@@ -9,7 +9,6 @@
align-items: flex-start; align-items: flex-start;
padding: 0.6rem 0; padding: 0.6rem 0;
width: 3rem; width: 3rem;
cursor: pointer;
.wrapper { .wrapper {
display: flex; display: flex;

View File

@@ -49,7 +49,6 @@
right: 1rem; right: 1rem;
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
cursor: pointer;
opacity: 0; opacity: 0;
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
visibility: hidden; visibility: hidden;

View File

@@ -3,7 +3,6 @@
user-select: none; user-select: none;
z-index: 2; z-index: 2;
padding: 8px 36px 8px 8px; padding: 8px 36px 8px 8px;
cursor: pointer;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;

View File

@@ -2,7 +2,6 @@
position: fixed; position: fixed;
user-select: none; user-select: none;
z-index: 2; z-index: 2;
cursor: pointer;
height: 2rem; height: 2rem;
right: 2rem; right: 2rem;
bottom: 1rem; bottom: 1rem;

View File

@@ -21,6 +21,7 @@ export default function Switch(props) {
props.handleOnClick(!on) props.handleOnClick(!on)
} }
}} }}
data-type="clickable"
> >
<span className={classes.text}>{i18n(props.text)}</span> <span className={classes.text}>{i18n(props.text)}</span>
<section className={classes.wrapper}> <section className={classes.wrapper}>

View File

@@ -52,6 +52,7 @@ export default function ToTopButton(props) {
<> <>
<section className={`${classes['totop-button']} ${clicked ? classes.clicked : ''} ${hidden ? '' : classes.show} ${props.className ? props.className : ''}`} <section className={`${classes['totop-button']} ${clicked ? classes.clicked : ''} ${hidden ? '' : classes.show} ${props.className ? props.className : ''}`}
onClick={() => { smoothScroll("#root") }} onClick={() => { smoothScroll("#root") }}
data-type="clickable"
> >
<section className={classes.bar}></section> <section className={classes.bar}></section>
<section className={classes.bar}></section> <section className={classes.bar}></section>

View File

@@ -20,6 +20,7 @@ import { useHeader } from '@/state/header';
import VoiceElement from '@/component/voice'; import VoiceElement from '@/component/voice';
import spine from '!/libs/spine-player' import spine from '!/libs/spine-player'
import '!/libs/spine-player.css' import '!/libs/spine-player.css'
import AnimatedCursor from "react-animated-cursor"
import useUmami from '@/state/insights'; import useUmami from '@/state/insights';
const voiceOnAtom = atomWithStorage('voiceOn', false) const voiceOnAtom = atomWithStorage('voiceOn', false)
@@ -186,6 +187,25 @@ export default function Error() {
handleAduioStateChange={handleAduioStateChange} handleAduioStateChange={handleAduioStateChange}
/> />
</main> </main>
<AnimatedCursor
innerSize={8}
outerSize={36}
innerScale={1}
outerScale={0.5}
outerAlpha={0.7}
hasBlendMode={true}
innerStyle={{
backgroundColor: 'var(--cursor-color)'
}}
outerStyle={{
backgroundColor: 'transparent',
border: '3px solid var(--cursor-color)'
}}
clickables={[
'a',
'section[data-type="clickable"]',
]}
/>
</section> </section>
); );
} }

View File

@@ -2,7 +2,7 @@ import React, {
useState, useState,
useEffect, useEffect,
useMemo, useMemo,
useCallback useCallback,
} from 'react' } from 'react'
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
@@ -29,6 +29,7 @@ import Popup from '@/component/popup';
import Border from '@/component/border'; import Border from '@/component/border';
import CharIcon from '@/component/char_icon'; import CharIcon from '@/component/char_icon';
import ToTopButton from '@/component/totop_button'; import ToTopButton from '@/component/totop_button';
import AnimatedCursor from "react-animated-cursor"
const currentYear = new Date().getFullYear() const currentYear = new Date().getFullYear()
@@ -79,8 +80,7 @@ export default function Root() {
setTimeout(() => { setTimeout(() => {
document.querySelector('.loader').style.display = 'none' document.querySelector('.loader').style.display = 'none'
}, 500) }, 500)
} }, [])
, [])
return ( return (
<> <>
@@ -88,6 +88,7 @@ export default function Root() {
<section <section
className={`${header.navButton} ${drawerHidden ? '' : header.active}`} className={`${header.navButton} ${drawerHidden ? '' : header.active}`}
onClick={() => toggleDrawer()} onClick={() => toggleDrawer()}
data-type="clickable"
> >
<section className={header.bar} /> <section className={header.bar} />
<section className={header.bar} /> <section className={header.bar} />
@@ -138,6 +139,25 @@ export default function Root() {
<ScrollRestoration /> <ScrollRestoration />
</main> </main>
<FooterElement /> <FooterElement />
<AnimatedCursor
innerSize={8}
outerSize={36}
innerScale={1}
outerScale={0.5}
outerAlpha={0.7}
hasBlendMode={true}
innerStyle={{
backgroundColor: 'var(--cursor-color)'
}}
outerStyle={{
backgroundColor: 'transparent',
border: '3px solid var(--cursor-color)'
}}
clickables={[
'a',
'section[data-type="clickable"]',
]}
/>
</> </>
) )
} }
@@ -150,7 +170,7 @@ function FooterElement() {
return ( return (
<footer className={footer.footer}> <footer className={footer.footer}>
<section className={`${footer.links} ${footer.section}`}> <section className={`${footer.links} ${footer.section}`}>
<section className={footer.item}> <section className={footer.item} data-type="clickable">
<Popup <Popup
className={footer.link} className={footer.link}
title={i18n('disclaimer')} title={i18n('disclaimer')}
@@ -164,7 +184,7 @@ function FooterElement() {
<section className={footer.item}> <section className={footer.item}>
<Link reloadDocument to="https://gura.ch/aklive2d-gh" target="_blank" className={footer.link}>GitHub</Link> <Link reloadDocument to="https://gura.ch/aklive2d-gh" target="_blank" className={footer.link}>GitHub</Link>
</section> </section>
<section className={footer.item}> <section className={footer.item} data-type="clickable">
<Popup <Popup
className={footer.link} className={footer.link}
title={i18n('contact_us')} title={i18n('contact_us')}
@@ -268,6 +288,7 @@ function HeaderTabsElement({ item }) {
item.onClick && item.onClick(e, currentTab) item.onClick && item.onClick(e, currentTab)
}} }}
style={item.style} style={item.style}
data-type="clickable"
> >
<section className={classes['text-wrapper']}> <section className={classes['text-wrapper']}>
<span>{i18n(item.key)}</span> <span>{i18n(item.key)}</span>
@@ -306,7 +327,7 @@ function HeaderButton() {
) )
} else { } else {
return ( return (
<section className={header['back-arrow']}> <section className={header['back-arrow']} data-type="clickable">
<Link to="/" className={header.link}> <Link to="/" className={header.link}>
<section className={header.arrow1} /> <section className={header.arrow1} />
<section className={header.arrow2} /> <section className={header.arrow2} />

View File

@@ -28,6 +28,7 @@ export default function Changelogs() {
setExtraArea([]) setExtraArea([])
setFastNavigation([]) setFastNavigation([])
setHeaderIcon(null) setHeaderIcon(null)
document.documentElement.style.setProperty('--cursor-color', 'var(--text-color)');
}, [setExtraArea, setFastNavigation, setHeaderIcon, setTitle]) }, [setExtraArea, setFastNavigation, setHeaderIcon, setTitle])
useEffect(() => { useEffect(() => {

View File

@@ -64,6 +64,7 @@ export default function Home() {
useEffect(() => { useEffect(() => {
setContent(config?.operators || []) setContent(config?.operators || [])
document.documentElement.style.setProperty('--cursor-color', 'var(--text-color)');
}, [config]) }, [config])
const handleAduioStateChange = useCallback((e, state) => { const handleAduioStateChange = useCallback((e, state) => {
@@ -258,6 +259,15 @@ export default function Home() {
function OperatorElement({ item, hidden, handleVoicePlay }) { function OperatorElement({ item, hidden, handleVoicePlay }) {
const { textDefaultLang, language, alternateLang } = useLanguage() const { textDefaultLang, language, alternateLang } = useLanguage()
const onMouseEnter = useCallback(() => {
handleVoicePlay(`/${item.link}/assets/${JSON.parse(import.meta.env.VITE_VOICE_FOLDERS).main}/${import.meta.env.VITE_APP_VOICE_URL}`)
document.documentElement.style.setProperty('--cursor-color', item.color);
}, [handleVoicePlay, item.color, item.link])
const onMouseLeave = useCallback(() => {
document.documentElement.style.setProperty('--cursor-color', 'var(--text-color)');
}, [])
return useMemo(() => { return useMemo(() => {
return ( return (
<NavLink <NavLink
@@ -266,7 +276,8 @@ function OperatorElement({ item, hidden, handleVoicePlay }) {
hidden={hidden} hidden={hidden}
> >
<section <section
onMouseEnter={() => handleVoicePlay(`/${item.link}/assets/${JSON.parse(import.meta.env.VITE_VOICE_FOLDERS).main}/${import.meta.env.VITE_APP_VOICE_URL}`)} onMouseEnter={() => onMouseEnter()}
onMouseLeave={() => onMouseLeave()}
> >
<section className={classes['background-filler']} /> <section className={classes['background-filler']} />
<section className={classes.outline} /> <section className={classes.outline} />
@@ -296,7 +307,7 @@ function OperatorElement({ item, hidden, handleVoicePlay }) {
</section> </section>
</NavLink> </NavLink>
) )
}, [item, hidden, language, alternateLang, textDefaultLang, handleVoicePlay]) }, [item, hidden, language, alternateLang, textDefaultLang, onMouseEnter, onMouseLeave])
} }
function VoiceSwitchElement({ src, replay, handleAduioStateChange }) { function VoiceSwitchElement({ src, replay, handleAduioStateChange }) {

View File

@@ -130,6 +130,7 @@ export default function Operator() {
fetch(`/${import.meta.env.VITE_DIRECTORY_FOLDER}/voice_${config.link}.json`).then(res => res.json()).then(data => { fetch(`/${import.meta.env.VITE_DIRECTORY_FOLDER}/voice_${config.link}.json`).then(res => res.json()).then(data => {
setVoiceConfig(data) setVoiceConfig(data)
}) })
document.documentElement.style.setProperty('--cursor-color', config.color);
} }
}, [key, operators, setHeaderIcon]) }, [key, operators, setHeaderIcon])
@@ -411,7 +412,7 @@ export default function Operator() {
<section className={classes['styled-selection']}> <section className={classes['styled-selection']}>
{item.options.map((option) => { {item.options.map((option) => {
return ( return (
<section className={`${classes.content} ${option.activeRule && option.activeRule() ? classes.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} data-type="clickable">
<section className={classes.option}> <section className={classes.option}>
<section className={classes.outline} /> <section className={classes.outline} />
<section className={`${classes.text} ${classes['no-overflow']}`}>{i18n(option.name)}</section> <section className={`${classes.text} ${classes['no-overflow']}`}>{i18n(option.name)}</section>

View File

@@ -66,9 +66,9 @@
.item { .item {
position: relative; position: relative;
cursor: pointer;
width: 12rem; width: 12rem;
margin: 1.25rem; margin: 1.25rem;
flex-shrink: 0;
background-image: repeating-linear-gradient( background-image: repeating-linear-gradient(
90deg, 90deg,
var(--home-item-background-linear-gradient-color) 0, var(--home-item-background-linear-gradient-color) 0,
@@ -202,7 +202,6 @@
.content { .content {
padding: 0.8rem 0; padding: 0.8rem 0;
cursor: pointer;
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;
.option { .option {

View File

@@ -55,10 +55,8 @@
font-weight: 700; font-weight: 700;
padding: 0 1rem; padding: 0 1rem;
text-transform: uppercase; text-transform: uppercase;
cursor: pointer;
border-bottom: 0.3rem solid transparent; border-bottom: 0.3rem solid transparent;
display: inline-block; display: inline-block;
cursor: pointer;
text-decoration: none; text-decoration: none;
.text-wrapper { .text-wrapper {
@@ -77,6 +75,9 @@
&.active { &.active {
border-bottom-color: currentColor; border-bottom-color: currentColor;
} }
@media only screen and (max-width: 430px) {
line-height: 2em;
}
} }
} }
@media only screen and (max-width: 430px) { @media only screen and (max-width: 430px) {

View File

@@ -36,7 +36,6 @@
font-size: 2rem; font-size: 2rem;
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
cursor: pointer;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;

View File

@@ -46,6 +46,7 @@
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-animated-cursor": "^2.11.2",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-refresh": "^0.14.0", "react-refresh": "^0.14.0",
"react-router-dom": "^6.17.0", "react-router-dom": "^6.17.0",

13
pnpm-lock.yaml generated
View File

@@ -23,6 +23,9 @@ dependencies:
react: react:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0 version: 18.2.0
react-animated-cursor:
specifier: ^2.11.2
version: 2.11.2(react-dom@18.2.0)(react@18.2.0)
react-dom: react-dom:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0(react@18.2.0) version: 18.2.0(react@18.2.0)
@@ -2978,6 +2981,16 @@ packages:
strip-json-comments: 2.0.1 strip-json-comments: 2.0.1
dev: false dev: false
/react-animated-cursor@2.11.2(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-LV0PtST61tdeW/chCqFt1JLp6i0d5fD9v2uradBcPAkKKCi2Iw7sAcD11ADS1TjXPPj9MOoD9YubEt++/IbZ+g==}
peerDependencies:
react: ^18.2.0
react-dom: ^18.2.0
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/react-dom@18.2.0(react@18.2.0): /react-dom@18.2.0(react@18.2.0):
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies: peerDependencies: