Merge branch 'supporters_page' into develop
@@ -0,0 +1,224 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import styles from "./Supporters.module.scss";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useSettingBoxScrollPosition,
|
||||||
|
} from "@logics_configs"
|
||||||
|
|
||||||
|
const supporter_images = import.meta.glob("@images/supporters/supporters_images/*.{png,jpg,jpeg,svg}", { eager: true });
|
||||||
|
const chato_expression_images = import.meta.glob("@images/supporters/chato_expressions/*.{png,jpg,jpeg,svg}", { eager: true });
|
||||||
|
import fanbox_img from "@images/supporters/c_fanbox_1620x580.png";
|
||||||
|
import vrct_supporters_title from "@images/supporters/vrct_supporters_title.png";
|
||||||
|
import fanbox_button from "@images/supporters/fanbox_button.png";
|
||||||
|
import kofi_preparing from "@images/supporters/kofi_preparing.png";
|
||||||
|
|
||||||
|
import ExternalLink from "@images/external_link.svg?react";
|
||||||
|
|
||||||
|
const mogu_count = 8;
|
||||||
|
const mochi_count = 3;
|
||||||
|
const fuwa_count = 4;
|
||||||
|
const basic_count = 5;
|
||||||
|
const former_count = 2;
|
||||||
|
const and_you_count = 1;
|
||||||
|
const default_icon_numbers = ["05", "06", "07", "11"];
|
||||||
|
|
||||||
|
const supporters_filenames = Array.from({ length: 23 }, (_, index) => `supporter_${String(index + 1).padStart(2, "0")}`);
|
||||||
|
const chato_expressions_filenames = Array.from({ length: 7 }, (_, index) => `chato_expression_${String(index + 1).padStart(2, "0")}`);
|
||||||
|
|
||||||
|
const SHUFFLE_INTERVAL_TIME = 20000;
|
||||||
|
const shuffleArray = (array) => {
|
||||||
|
return array
|
||||||
|
.map((value) => ({ value, sort: Math.random() }))
|
||||||
|
.sort((a, b) => a.sort - b.sort)
|
||||||
|
.map(({ value }) => value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Supporters = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<SupportUsContainer />
|
||||||
|
<SupportersContainer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SupportUsContainer = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.support_us_container}>
|
||||||
|
<img className={styles.fanbox_img} src={fanbox_img} />
|
||||||
|
<div className={styles.support_us_button_wrapper}>
|
||||||
|
<div className={styles.fanbox_wrapper}>
|
||||||
|
<a className={styles.fanbox_button} href="https://vrct-dev.fanbox.cc/" target="_blank" rel="noreferrer">
|
||||||
|
<img style={{ height: "100%", width: "100%", objectFit: "contain" }} src={fanbox_button} />
|
||||||
|
</a>
|
||||||
|
<p className={styles.mainly_japanese}>日本語 / Mainly Japanese</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.kofi_wrapper}>
|
||||||
|
<img className={styles.kofi_preparing} src={kofi_preparing} />
|
||||||
|
<p className={styles.mainly_english}>Mainly English</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRandomImage = (images) => {
|
||||||
|
const random_index = Math.floor(Math.random() * images.length);
|
||||||
|
return images[random_index];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SupportersContainer = () => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.supporters_container}>
|
||||||
|
<img className={styles.vrct_supporters_title} src={vrct_supporters_title} />
|
||||||
|
<p className={styles.vrct_supporters_desc}>{`VRCT3.0のアップデートに向けて、めちゃ大変な開発を支えてくれた "Early Supporters" です。\nThey are the 'Early Supporters' who supported us through the incredibly challenging development toward the VRCT3.0 update.`}</p>
|
||||||
|
<ProgressBar />
|
||||||
|
<SupportsWrapper />
|
||||||
|
<ProgressBar />
|
||||||
|
<p className={styles.vrct_supporters_desc_end}>{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(in開発室) しいなは喜び庭駆け回っています!!!ふわもちもぐもぐです!ありがとうございます。これからもまだまだ進化するVRCTをどうかよろしくお願いします!\nThanks to everyone, Misha has been granted the privilege of sleeping in a proper bed (in the development room), and Shiina is so happy, running around the yard! Fuwa-mochi-mogu-mogu! Thank you so much! We hope you'll continue to support the ever-evolving VRCT!`}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProgressBar = () => {
|
||||||
|
const [is_active, setIsActive] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsActive(true);
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setIsActive(false);
|
||||||
|
setTimeout(() => setIsActive(true), 50);
|
||||||
|
}, SHUFFLE_INTERVAL_TIME);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.progress_bar, {
|
||||||
|
[styles.progress_bar_active]: is_active,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SupportsWrapper = () => {
|
||||||
|
const { saveScrollPosition, restoreScrollPosition } = useSettingBoxScrollPosition();
|
||||||
|
const [imagesState, setImagesState] = useState({
|
||||||
|
mogu_images: [],
|
||||||
|
mochi_images: [],
|
||||||
|
fuwa_images: [],
|
||||||
|
basic_images: [],
|
||||||
|
former_images: [],
|
||||||
|
and_you_images: [],
|
||||||
|
chato_images: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const shuffleImages = () => {
|
||||||
|
saveScrollPosition();
|
||||||
|
const getCategoryImages = (start, count) => {
|
||||||
|
const category_images = supporters_filenames.slice(start, start + count);
|
||||||
|
return shuffleArray(category_images);
|
||||||
|
};
|
||||||
|
|
||||||
|
const randomChatoImages = shuffleArray(
|
||||||
|
Array.from({ length: mogu_count + mochi_count + fuwa_count + basic_count + former_count }, () =>
|
||||||
|
getRandomImage(chato_expressions_filenames)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
setImagesState({
|
||||||
|
mogu_images: getCategoryImages(0, mogu_count),
|
||||||
|
mochi_images: getCategoryImages(mogu_count, mochi_count),
|
||||||
|
fuwa_images: getCategoryImages(mogu_count + mochi_count, fuwa_count),
|
||||||
|
basic_images: getCategoryImages(mogu_count + mochi_count + fuwa_count, basic_count),
|
||||||
|
former_images: getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count, former_count),
|
||||||
|
and_you_images: getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count + former_count, and_you_count),
|
||||||
|
chato_images: randomChatoImages,
|
||||||
|
});
|
||||||
|
setTimeout(() => restoreScrollPosition(), 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
shuffleImages();
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
shuffleImages();
|
||||||
|
}, SHUFFLE_INTERVAL_TIME);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
const getSupportersImageByFileName = (file_name) => {
|
||||||
|
const image_path = Object.keys(supporter_images).find((path) => path.endsWith(file_name + ".png"));
|
||||||
|
return image_path ? supporter_images[image_path]?.default : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getChatoImageByFileName = (file_name) => {
|
||||||
|
const image_path = Object.keys(chato_expression_images).find((path) => path.endsWith(file_name + ".png"));
|
||||||
|
return image_path ? chato_expression_images[image_path]?.default : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRandomDelay = (min, max) => {
|
||||||
|
const random_value = Math.random() * (max - min) + min;
|
||||||
|
return `${random_value.toFixed(1)}s`;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const renderImages = (image_list, chato_list, options = {}) => {
|
||||||
|
return image_list.map((file_name, index) => {
|
||||||
|
const img_src = getSupportersImageByFileName(file_name);
|
||||||
|
const is_default_icon = default_icon_numbers.some((element) => file_name.endsWith(element));
|
||||||
|
const chato_expression_src = is_default_icon ? getChatoImageByFileName(chato_list[index]) : null;
|
||||||
|
const random_delay = getRandomDelay(0.1, 6);
|
||||||
|
|
||||||
|
return img_src ? (
|
||||||
|
<div
|
||||||
|
key={file_name}
|
||||||
|
className={clsx(styles.supporter_image_wrapper, options.class_name)}
|
||||||
|
style={{ "--delay": random_delay }}
|
||||||
|
>
|
||||||
|
<img className={styles.supporter_image} src={img_src} />
|
||||||
|
{chato_expression_src && (
|
||||||
|
<img
|
||||||
|
className={styles.default_chato_expression_image}
|
||||||
|
src={chato_expression_src}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{options.is_and_you_icon ? <AndYouIcon /> : null}
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.supporters_wrapper}>
|
||||||
|
{renderImages(imagesState.mogu_images, imagesState.chato_images, { class_name: styles.mogu_image })}
|
||||||
|
{renderImages(imagesState.mochi_images, imagesState.chato_images)}
|
||||||
|
{renderImages(imagesState.fuwa_images, imagesState.chato_images)}
|
||||||
|
{renderImages(imagesState.basic_images, imagesState.chato_images)}
|
||||||
|
{renderImages(imagesState.former_images, imagesState.chato_images)}
|
||||||
|
<a href="https://vrct-dev.fanbox.cc/" target="_blank" rel="noreferrer">
|
||||||
|
{renderImages(imagesState.and_you_images, imagesState.chato_images, { is_and_you_icon: true, class_name: styles.and_you_image })}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AndYouIcon = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.and_you_container}>
|
||||||
|
<div className={styles.and_you_1}></div>
|
||||||
|
<div className={styles.and_you_2}></div>
|
||||||
|
</div>
|
||||||
|
<p className={styles.and_you_fanbox_link_text}>
|
||||||
|
FANBOX
|
||||||
|
<ExternalLink className={styles.external_link_svg} />
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
gap: 1.2rem;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
// background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_us_container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
.fanbox_img {
|
||||||
|
width: 60vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_us_button_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: 4.8rem;
|
||||||
|
}
|
||||||
|
.fanbox_wrapper, .kofi_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.fanbox_button {
|
||||||
|
width: 14rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
&:hover {
|
||||||
|
width: 16rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.kofi_preparing {
|
||||||
|
width: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainly_japanese, .mainly_english {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--dark_400_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.supporters_container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.vrct_supporters_title {
|
||||||
|
height: 6rem;
|
||||||
|
}
|
||||||
|
.vrct_supporters_desc {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporters_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: 1.4rem;
|
||||||
|
row-gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 18rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mogu_image {
|
||||||
|
position: relative;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -200%;
|
||||||
|
left: -200%;
|
||||||
|
width: 300%;
|
||||||
|
height: 300%;
|
||||||
|
background: linear-gradient(45deg, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0) 70%);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
animation: shine 2.5s infinite;
|
||||||
|
filter: blur(0.4rem);
|
||||||
|
animation-delay: var(--delay, 0s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shine {
|
||||||
|
0% {
|
||||||
|
top: -200%;
|
||||||
|
left: -200%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
top: 200%;
|
||||||
|
left: 200%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.default_chato_expression_image {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 2.9rem;
|
||||||
|
width: 2.8rem;
|
||||||
|
transform: translate(-50%, -50%) rotate(10deg);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.and_you_container {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.and_you_1, .and_you_2 {
|
||||||
|
width: 2.2rem;
|
||||||
|
height: 0.2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--dark_400_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.and_you_2 {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_wrapper {
|
||||||
|
&:hover .and_you_container {
|
||||||
|
top: 40%;
|
||||||
|
transform: translate(-50%, -50%) rotate(180deg);
|
||||||
|
}
|
||||||
|
&:hover .and_you_fanbox_link_text {
|
||||||
|
top: 70%;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_wrapper.and_you_image {
|
||||||
|
cursor: pointer;
|
||||||
|
&:active {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.and_you_fanbox_link_text {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--dark_400_color);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.external_link_svg {
|
||||||
|
color: var(--dark_200_color);
|
||||||
|
width: 1.2rem;
|
||||||
|
margin-left: 0.6rem;
|
||||||
|
padding-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vrct_supporters_desc_end {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: var(--dark_300_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress_bar {
|
||||||
|
height: 0.2rem;
|
||||||
|
width: 0%;
|
||||||
|
&.progress_bar_active {
|
||||||
|
transition: width 20000ms linear;
|
||||||
|
background-color: var(--primary_400_color);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,39 +1,6 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
import styles from "./Supporters.module.scss";
|
import styles from "./Supporters.module.scss";
|
||||||
import clsx from "clsx";
|
import { SupportUsContainer } from "./support_us_container/SupportUsContainer";
|
||||||
import { useTranslation } from "react-i18next";
|
import { SupportersContainer } from "./supporters_container/SupportersContainer";
|
||||||
|
|
||||||
import {
|
|
||||||
useSettingBoxScrollPosition,
|
|
||||||
} from "@logics_configs"
|
|
||||||
|
|
||||||
const supporter_images = import.meta.glob("@images/supporters/supporters_images/*.{png,jpg,jpeg,svg}", { eager: true });
|
|
||||||
const chato_expression_images = import.meta.glob("@images/supporters/chato_expressions/*.{png,jpg,jpeg,svg}", { eager: true });
|
|
||||||
import fanbox_img from "@images/supporters/c_fanbox_1620x580.png";
|
|
||||||
import vrct_supporters_title from "@images/supporters/vrct_supporters_title.png";
|
|
||||||
import fanbox_button from "@images/supporters/fanbox_button.png";
|
|
||||||
import kofi_preparing from "@images/supporters/kofi_preparing.png";
|
|
||||||
|
|
||||||
import ExternalLink from "@images/external_link.svg?react";
|
|
||||||
|
|
||||||
const mogu_count = 8;
|
|
||||||
const mochi_count = 3;
|
|
||||||
const fuwa_count = 4;
|
|
||||||
const basic_count = 5;
|
|
||||||
const former_count = 2;
|
|
||||||
const and_you_count = 1;
|
|
||||||
const default_icon_numbers = ["05", "06", "07", "11"];
|
|
||||||
|
|
||||||
const supporters_filenames = Array.from({ length: 23 }, (_, index) => `supporter_${String(index + 1).padStart(2, "0")}`);
|
|
||||||
const chato_expressions_filenames = Array.from({ length: 7 }, (_, index) => `chato_expression_${String(index + 1).padStart(2, "0")}`);
|
|
||||||
|
|
||||||
const SHUFFLE_INTERVAL_TIME = 20000;
|
|
||||||
const shuffleArray = (array) => {
|
|
||||||
return array
|
|
||||||
.map((value) => ({ value, sort: Math.random() }))
|
|
||||||
.sort((a, b) => a.sort - b.sort)
|
|
||||||
.map(({ value }) => value);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Supporters = () => {
|
export const Supporters = () => {
|
||||||
return (
|
return (
|
||||||
@@ -43,182 +10,3 @@ export const Supporters = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SupportUsContainer = () => {
|
|
||||||
return (
|
|
||||||
<div className={styles.support_us_container}>
|
|
||||||
<img className={styles.fanbox_img} src={fanbox_img} />
|
|
||||||
<div className={styles.support_us_button_wrapper}>
|
|
||||||
<div className={styles.fanbox_wrapper}>
|
|
||||||
<a className={styles.fanbox_button} href="https://vrct-dev.fanbox.cc/" target="_blank" rel="noreferrer">
|
|
||||||
<img style={{ height: "100%", width: "100%", objectFit: "contain" }} src={fanbox_button} />
|
|
||||||
</a>
|
|
||||||
<p className={styles.mainly_japanese}>日本語 / Mainly Japanese</p>
|
|
||||||
</div>
|
|
||||||
<div className={styles.kofi_wrapper}>
|
|
||||||
<img className={styles.kofi_preparing} src={kofi_preparing} />
|
|
||||||
<p className={styles.mainly_english}>Mainly English</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRandomImage = (images) => {
|
|
||||||
const random_index = Math.floor(Math.random() * images.length);
|
|
||||||
return images[random_index];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SupportersContainer = () => {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.supporters_container}>
|
|
||||||
<img className={styles.vrct_supporters_title} src={vrct_supporters_title} />
|
|
||||||
<p className={styles.vrct_supporters_desc}>{`VRCT3.0のアップデートに向けて、めちゃ大変な開発を支えてくれた "Early Supporters" です。\nThey are the 'Early Supporters' who supported us through the incredibly challenging development toward the VRCT3.0 update.`}</p>
|
|
||||||
<ProgressBar />
|
|
||||||
<SupportsWrapper />
|
|
||||||
<ProgressBar />
|
|
||||||
<p className={styles.vrct_supporters_desc_end}>{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(in開発室) しいなは喜び庭駆け回っています!!!ふわもちもぐもぐです!ありがとうございます。これからもまだまだ進化するVRCTをどうかよろしくお願いします!\nThanks to everyone, Misha has been granted the privilege of sleeping in a proper bed (in the development room), and Shiina is so happy, running around the yard! Fuwa-mochi-mogu-mogu! Thank you so much! We hope you'll continue to support the ever-evolving VRCT!`}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProgressBar = () => {
|
|
||||||
const [is_active, setIsActive] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsActive(true);
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setIsActive(false);
|
|
||||||
setTimeout(() => setIsActive(true), 50);
|
|
||||||
}, SHUFFLE_INTERVAL_TIME);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={clsx(styles.progress_bar, {
|
|
||||||
[styles.progress_bar_active]: is_active,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SupportsWrapper = () => {
|
|
||||||
const { saveScrollPosition, restoreScrollPosition } = useSettingBoxScrollPosition();
|
|
||||||
const [imagesState, setImagesState] = useState({
|
|
||||||
mogu_images: [],
|
|
||||||
mochi_images: [],
|
|
||||||
fuwa_images: [],
|
|
||||||
basic_images: [],
|
|
||||||
former_images: [],
|
|
||||||
and_you_images: [],
|
|
||||||
chato_images: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const shuffleImages = () => {
|
|
||||||
saveScrollPosition();
|
|
||||||
const getCategoryImages = (start, count) => {
|
|
||||||
const category_images = supporters_filenames.slice(start, start + count);
|
|
||||||
return shuffleArray(category_images);
|
|
||||||
};
|
|
||||||
|
|
||||||
const randomChatoImages = shuffleArray(
|
|
||||||
Array.from({ length: mogu_count + mochi_count + fuwa_count + basic_count + former_count }, () =>
|
|
||||||
getRandomImage(chato_expressions_filenames)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
setImagesState({
|
|
||||||
mogu_images: getCategoryImages(0, mogu_count),
|
|
||||||
mochi_images: getCategoryImages(mogu_count, mochi_count),
|
|
||||||
fuwa_images: getCategoryImages(mogu_count + mochi_count, fuwa_count),
|
|
||||||
basic_images: getCategoryImages(mogu_count + mochi_count + fuwa_count, basic_count),
|
|
||||||
former_images: getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count, former_count),
|
|
||||||
and_you_images: getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count + former_count, and_you_count),
|
|
||||||
chato_images: randomChatoImages,
|
|
||||||
});
|
|
||||||
setTimeout(() => restoreScrollPosition(), 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
shuffleImages();
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
shuffleImages();
|
|
||||||
}, SHUFFLE_INTERVAL_TIME);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
const getSupportersImageByFileName = (file_name) => {
|
|
||||||
const image_path = Object.keys(supporter_images).find((path) => path.endsWith(file_name + ".png"));
|
|
||||||
return image_path ? supporter_images[image_path]?.default : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getChatoImageByFileName = (file_name) => {
|
|
||||||
const image_path = Object.keys(chato_expression_images).find((path) => path.endsWith(file_name + ".png"));
|
|
||||||
return image_path ? chato_expression_images[image_path]?.default : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRandomDelay = (min, max) => {
|
|
||||||
const random_value = Math.random() * (max - min) + min;
|
|
||||||
return `${random_value.toFixed(1)}s`;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const renderImages = (image_list, chato_list, options = {}) => {
|
|
||||||
return image_list.map((file_name, index) => {
|
|
||||||
const img_src = getSupportersImageByFileName(file_name);
|
|
||||||
const is_default_icon = default_icon_numbers.some((element) => file_name.endsWith(element));
|
|
||||||
const chato_expression_src = is_default_icon ? getChatoImageByFileName(chato_list[index]) : null;
|
|
||||||
const random_delay = getRandomDelay(0.1, 6);
|
|
||||||
|
|
||||||
return img_src ? (
|
|
||||||
<div
|
|
||||||
key={file_name}
|
|
||||||
className={clsx(styles.supporter_image_wrapper, options.class_name)}
|
|
||||||
style={{ "--delay": random_delay }}
|
|
||||||
>
|
|
||||||
<img className={styles.supporter_image} src={img_src} />
|
|
||||||
{chato_expression_src && (
|
|
||||||
<img
|
|
||||||
className={styles.default_chato_expression_image}
|
|
||||||
src={chato_expression_src}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{options.is_and_you_icon ? <AndYouIcon /> : null}
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.supporters_wrapper}>
|
|
||||||
{renderImages(imagesState.mogu_images, imagesState.chato_images, { class_name: styles.mogu_image })}
|
|
||||||
{renderImages(imagesState.mochi_images, imagesState.chato_images)}
|
|
||||||
{renderImages(imagesState.fuwa_images, imagesState.chato_images)}
|
|
||||||
{renderImages(imagesState.basic_images, imagesState.chato_images)}
|
|
||||||
{renderImages(imagesState.former_images, imagesState.chato_images)}
|
|
||||||
<a href="https://vrct-dev.fanbox.cc/" target="_blank" rel="noreferrer">
|
|
||||||
{renderImages(imagesState.and_you_images, imagesState.chato_images, { is_and_you_icon: true, class_name: styles.and_you_image })}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AndYouIcon = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={styles.and_you_container}>
|
|
||||||
<div className={styles.and_you_1}></div>
|
|
||||||
<div className={styles.and_you_2}></div>
|
|
||||||
</div>
|
|
||||||
<p className={styles.and_you_fanbox_link_text}>
|
|
||||||
FANBOX
|
|
||||||
<ExternalLink className={styles.external_link_svg} />
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,205 +1,8 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1.2rem;
|
gap: 3.2rem;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
// background-color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.support_us_container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2rem;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
}
|
|
||||||
.fanbox_img {
|
|
||||||
width: 60vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.support_us_button_wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
gap: 4.8rem;
|
|
||||||
}
|
|
||||||
.fanbox_wrapper, .kofi_wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
.fanbox_button {
|
|
||||||
width: 14rem;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
&:hover {
|
|
||||||
width: 16rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.kofi_preparing {
|
|
||||||
width: 6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainly_japanese, .mainly_english {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: var(--dark_400_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.supporters_container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
.vrct_supporters_title {
|
|
||||||
height: 6rem;
|
|
||||||
}
|
|
||||||
.vrct_supporters_desc {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
text-align: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.supporters_wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
align-content: start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
column-gap: 1.4rem;
|
|
||||||
row-gap: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.supporter_image_wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 18rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.supporter_image {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mogu_image {
|
|
||||||
position: relative;
|
|
||||||
&::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: -200%;
|
|
||||||
left: -200%;
|
|
||||||
width: 300%;
|
|
||||||
height: 300%;
|
|
||||||
background: linear-gradient(45deg, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0) 70%);
|
|
||||||
transform: rotate(90deg);
|
|
||||||
animation: shine 2.5s infinite;
|
|
||||||
filter: blur(0.4rem);
|
|
||||||
animation-delay: var(--delay, 0s);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shine {
|
|
||||||
0% {
|
|
||||||
top: -200%;
|
|
||||||
left: -200%;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
top: 200%;
|
|
||||||
left: 200%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.default_chato_expression_image {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 2.9rem;
|
|
||||||
width: 2.8rem;
|
|
||||||
transform: translate(-50%, -50%) rotate(10deg);
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.and_you_container {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.and_you_1, .and_you_2 {
|
|
||||||
width: 2.2rem;
|
|
||||||
height: 0.2rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--dark_400_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.and_you_2 {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%) rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.supporter_image_wrapper {
|
|
||||||
&:hover .and_you_container {
|
|
||||||
top: 40%;
|
|
||||||
transform: translate(-50%, -50%) rotate(180deg);
|
|
||||||
}
|
|
||||||
&:hover .and_you_fanbox_link_text {
|
|
||||||
top: 70%;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.supporter_image_wrapper.and_you_image {
|
|
||||||
cursor: pointer;
|
|
||||||
&:active {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.and_you_fanbox_link_text {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: var(--dark_400_color);
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.external_link_svg {
|
|
||||||
color: var(--dark_200_color);
|
|
||||||
width: 1.2rem;
|
|
||||||
margin-left: 0.6rem;
|
|
||||||
padding-bottom: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrct_supporters_desc_end {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
margin-top: 2rem;
|
|
||||||
color: var(--dark_300_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress_bar {
|
|
||||||
height: 0.2rem;
|
|
||||||
width: 0%;
|
|
||||||
&.progress_bar_active {
|
|
||||||
transition: width 20000ms linear;
|
|
||||||
background-color: var(--primary_400_color);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import top_img from "@images/supporters/patreon_1600x400px.png";
|
||||||
|
import fanbox_logo from "@images/supporters/fanbox_logo.png";
|
||||||
|
import kofi_logo from "@images/supporters/kofi_logo.png";
|
||||||
|
import patreon_logo from "@images/supporters/patreon_logo.png";
|
||||||
|
import styles from "./SupportUsContainer.module.scss";
|
||||||
|
import { clsx } from "clsx";
|
||||||
|
|
||||||
|
export const SupportUsContainer = () => {
|
||||||
|
return (
|
||||||
|
<div id="support_us_container" className={styles.support_us_container}>
|
||||||
|
<img className={styles.top_img} src={top_img} />
|
||||||
|
<div className={styles.support_buttons_wrapper}>
|
||||||
|
<div className={styles.support_us_button_wrapper}>
|
||||||
|
<a className={styles.support_button} href="https://vrct-dev.fanbox.cc" target="_blank" rel="noreferrer">
|
||||||
|
<img
|
||||||
|
src={fanbox_logo}
|
||||||
|
className={clsx(styles.support_img, styles.fanbox_logo)}
|
||||||
|
/>
|
||||||
|
<div className={styles.spiral_top}></div>
|
||||||
|
<div className={styles.spiral_bottom}></div>
|
||||||
|
</a>
|
||||||
|
<a className={styles.support_button} href="https://ko-fi.com/vrct_dev" target="_blank" rel="noreferrer">
|
||||||
|
<img
|
||||||
|
src={kofi_logo}
|
||||||
|
className={clsx(styles.support_img, styles.kofi_logo)}
|
||||||
|
/>
|
||||||
|
<div className={styles.spiral_top}></div>
|
||||||
|
<div className={styles.spiral_bottom}></div>
|
||||||
|
</a>
|
||||||
|
<a className={styles.support_button} href="https://www.patreon.com/vrct_dev" target="_blank" rel="noreferrer">
|
||||||
|
<img
|
||||||
|
src={patreon_logo}
|
||||||
|
className={clsx(styles.support_img, styles.patreon_logo)}
|
||||||
|
/>
|
||||||
|
<div className={styles.spiral_top}></div>
|
||||||
|
<div className={styles.spiral_bottom}></div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className={styles.lines_container}>
|
||||||
|
<div className={styles.line_basic}></div>
|
||||||
|
<div className={styles.line_fuwa}></div>
|
||||||
|
<div className={styles.line_mochi}></div>
|
||||||
|
<div className={styles.line_mogu}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
.support_us_container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.4rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.support_buttons_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top_img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lines_container {
|
||||||
|
display: flex;
|
||||||
|
gap: 3.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line_basic, .line_fuwa, .line_mochi, .line_mogu {
|
||||||
|
width: 8.6rem;
|
||||||
|
height: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line_basic {
|
||||||
|
background-color: var(--dark_800_color);
|
||||||
|
}
|
||||||
|
.line_fuwa {
|
||||||
|
background-color: #5788a2;
|
||||||
|
}
|
||||||
|
.line_mochi {
|
||||||
|
background-color: var(--received_300_color);
|
||||||
|
}
|
||||||
|
.line_mogu {
|
||||||
|
background-color: var(--dark_basic_text_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_us_button_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 3.6rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_button {
|
||||||
|
position: relative;
|
||||||
|
padding: 1.2rem 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_img {
|
||||||
|
&.fanbox_logo {
|
||||||
|
height: 1.8rem;
|
||||||
|
}
|
||||||
|
&.kofi_logo, &.patreon_logo {
|
||||||
|
height: 2.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.spiral_top::before,
|
||||||
|
.spiral_top::after,
|
||||||
|
.spiral_bottom::before,
|
||||||
|
.spiral_bottom::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
}
|
||||||
|
.spiral_top::before {
|
||||||
|
background: var(--dark_800_color);
|
||||||
|
box-shadow: 0 0 0.4rem 0 var(--dark_800_color);
|
||||||
|
}
|
||||||
|
.spiral_top::after {
|
||||||
|
background: #5788a2;
|
||||||
|
box-shadow: 0 0 0.4rem 0 #5788a2;
|
||||||
|
}
|
||||||
|
.spiral_bottom::before {
|
||||||
|
background: var(--received_300_color);
|
||||||
|
box-shadow: 0 0 0.4rem 0 var(--received_300_color);
|
||||||
|
}
|
||||||
|
.spiral_bottom::after {
|
||||||
|
background: var(--dark_basic_text_color);
|
||||||
|
box-shadow: 0 0 0.4rem 0 var(--dark_basic_text_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spiral_top::before {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0.1rem;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.spiral_top::after {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0.1rem;
|
||||||
|
}
|
||||||
|
.spiral_bottom::before {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0.1rem;
|
||||||
|
}
|
||||||
|
.spiral_bottom::after {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 0.1rem;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_button:hover
|
||||||
|
.spiral_top::before {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.support_button:hover
|
||||||
|
.spiral_top::after {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.support_button:hover
|
||||||
|
.spiral_bottom::before {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.support_button:hover
|
||||||
|
.spiral_bottom::after {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.supporters_container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.vrct_supporters_title {
|
||||||
|
height: 6rem;
|
||||||
|
}
|
||||||
|
.vrct_supporters_desc {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import styles from "./SupportersContainer.module.scss";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import vrct_supporters_title from "@images/supporters/vrct_supporters_title.png";
|
||||||
|
import { SupportersWrapper } from "./supporters_wrapper/SupportersWrapper";
|
||||||
|
import { clsx } from "clsx";
|
||||||
|
const SHUFFLE_INTERVAL_TIME = 20000;
|
||||||
|
|
||||||
|
export const SupportersContainer = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.supporters_container}>
|
||||||
|
<img className={styles.vrct_supporters_title} src={vrct_supporters_title} />
|
||||||
|
<ProgressBar />
|
||||||
|
<SupportersWrapper />
|
||||||
|
<ProgressBar />
|
||||||
|
<p className={styles.vrct_supporters_desc_end}>{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(in開発室) しいなは喜び庭駆け回っています!!!ふわもちもぐもぐです!ありがとうございます。これからもまだまだ進化するVRCTをどうかよろしくお願いします!\nThanks to everyone, Misha has been granted the privilege of sleeping in a proper bed (in the development room), and Shiina is so happy, running around the yard! Fuwa-mochi-mogu-mogu! Thank you so much! We hope you'll continue to support the ever-evolving VRCT!`}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProgressBar = () => {
|
||||||
|
const [is_active, setIsActive] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsActive(true);
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setIsActive(false);
|
||||||
|
setTimeout(() => setIsActive(true), 50);
|
||||||
|
}, SHUFFLE_INTERVAL_TIME);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.progress_bar, {
|
||||||
|
[styles.progress_bar_active]: is_active,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
.supporters_container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.vrct_supporters_title {
|
||||||
|
height: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vrct_supporters_desc_end {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: var(--dark_300_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress_bar {
|
||||||
|
height: 0.2rem;
|
||||||
|
width: 0%;
|
||||||
|
&.progress_bar_active {
|
||||||
|
transition: width 20000ms linear;
|
||||||
|
background-color: var(--primary_400_color);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
import React, { useState, useCallback, useEffect } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||||
|
import styles from "./SupportersWrapper.module.scss";
|
||||||
|
import { shuffleArray, randomIntMinMax, randomMinMax } from "@utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useSettingBoxScrollPosition,
|
||||||
|
} from "@logics_configs"
|
||||||
|
|
||||||
|
import json_data from "./data.json";
|
||||||
|
|
||||||
|
const target_supporting_month = "2025-01";
|
||||||
|
const calc_support_period = ["2024-10", "2024-11", "2024-12", "2025-01"];
|
||||||
|
|
||||||
|
const SHUFFLE_INTERVAL_TIME = 20000;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const and_you_data = {
|
||||||
|
supporter_id: "and_you",
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getImagePath = (images, file_name) => {
|
||||||
|
const image_path = Object.keys(images).find((path) => path.endsWith(`${file_name}.png`));
|
||||||
|
return image_path ? images[image_path]?.default : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const image_sets = {
|
||||||
|
supporter_cards: import.meta.glob("@images/supporters/supporter_cards/*.png", { eager: true }),
|
||||||
|
chato_expressions: import.meta.glob("@images/supporters/chato_expressions/*.png", { eager: true }),
|
||||||
|
supporters_labels: import.meta.glob("@images/supporters/supporters_labels/*.png", { eager: true }),
|
||||||
|
supporters_icons: import.meta.glob("@images/supporters/supporters_icons/*.png", { eager: true }),
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSupporterCard = (plan_name) => {
|
||||||
|
const card_map = {
|
||||||
|
"もぐもぐ_2000": "mogu_card",
|
||||||
|
"もちもち_1000": "mochi_card",
|
||||||
|
"ふわふわ_500": "fuwa_card",
|
||||||
|
"Basic_300": "basic_card",
|
||||||
|
};
|
||||||
|
return getImagePath(image_sets.supporter_cards, card_map[plan_name] || "basic_card");
|
||||||
|
};
|
||||||
|
|
||||||
|
const getChatoExpressionsPath = (file_name) =>
|
||||||
|
getImagePath(image_sets.chato_expressions, file_name);
|
||||||
|
|
||||||
|
const getSupportersLabelsPath = (file_name) =>
|
||||||
|
getImagePath(image_sets.supporters_labels, file_name);
|
||||||
|
|
||||||
|
const getSupportersIconsPath = (file_name) =>
|
||||||
|
getImagePath(image_sets.supporters_icons, file_name);
|
||||||
|
|
||||||
|
const chato_ex_count = Object.keys(image_sets.chato_expressions).length;
|
||||||
|
|
||||||
|
export const SupportersWrapper = () => {
|
||||||
|
const { saveScrollPosition, restoreScrollPosition } = useSettingBoxScrollPosition();
|
||||||
|
|
||||||
|
let credit_pending_count = 0;
|
||||||
|
const filtered_data = json_data.filter((supporter) => {
|
||||||
|
if (!supporter.supporter_id) return false;
|
||||||
|
|
||||||
|
const months = Object.keys(supporter).filter((key) => key.match(/^\d{4}-\d{2}$/));
|
||||||
|
const has_valid_month = months.some((month) => supporter[month]);
|
||||||
|
if (!has_valid_month) return false;
|
||||||
|
|
||||||
|
const basic_300_months = months.filter((month) => supporter[month] === "Basic_300");
|
||||||
|
const has_special_plan = months.some((month) => ["ふわふわ_500", "もちもち_1000", "もぐもぐ_2000"].includes(supporter[month]));
|
||||||
|
|
||||||
|
if (basic_300_months.length === 1 && !has_special_plan) {
|
||||||
|
credit_pending_count++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const grouped_data = {
|
||||||
|
もぐもぐ_2000: [],
|
||||||
|
もちもち_1000: [],
|
||||||
|
ふわふわ_500: [],
|
||||||
|
Basic_300: [],
|
||||||
|
empty: [],
|
||||||
|
and_you: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
filtered_data.forEach((supporter) => {
|
||||||
|
const value = supporter[target_supporting_month] || "empty";
|
||||||
|
if (grouped_data[value]) {
|
||||||
|
grouped_data[value].push(supporter);
|
||||||
|
} else {
|
||||||
|
grouped_data["empty"].push(supporter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [supportersData, setSupportersData] = useState(() => [
|
||||||
|
...grouped_data["もぐもぐ_2000"],
|
||||||
|
...grouped_data["もちもち_1000"],
|
||||||
|
...grouped_data["ふわふわ_500"],
|
||||||
|
...grouped_data["Basic_300"],
|
||||||
|
...grouped_data["empty"],
|
||||||
|
and_you_data,
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
const [chatoExpressions, setChatoExpressions] = useState(() =>
|
||||||
|
supportersData.map(() =>
|
||||||
|
getChatoExpressionsPath(`chato_expression_${randomIntMinMax(1, chato_ex_count)}`)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const shuffleSupporters = useCallback(() => {
|
||||||
|
saveScrollPosition();
|
||||||
|
const newSupportersData = [
|
||||||
|
...shuffleArray(grouped_data["もぐもぐ_2000"]),
|
||||||
|
...shuffleArray(grouped_data["もちもち_1000"]),
|
||||||
|
...shuffleArray(grouped_data["ふわふわ_500"]),
|
||||||
|
...shuffleArray(grouped_data["Basic_300"]),
|
||||||
|
...shuffleArray(grouped_data["empty"]),
|
||||||
|
and_you_data,
|
||||||
|
];
|
||||||
|
setSupportersData(newSupportersData);
|
||||||
|
|
||||||
|
|
||||||
|
setChatoExpressions(
|
||||||
|
newSupportersData.map(() =>
|
||||||
|
getChatoExpressionsPath(`chato_expression_${randomIntMinMax(1, chato_ex_count)}`)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setTimeout(() => restoreScrollPosition(), 0);
|
||||||
|
}, [grouped_data]);
|
||||||
|
|
||||||
|
const renderImages = () => {
|
||||||
|
return supportersData.map((item, index) => {
|
||||||
|
const target_plan = item[target_supporting_month];
|
||||||
|
const img_src = getSupporterCard(target_plan);
|
||||||
|
const is_default_icon = item.supporter_icon_id === "";
|
||||||
|
const is_icon_plan = ["もぐもぐ_2000", "もちもち_1000"].includes(target_plan);
|
||||||
|
const is_and_you = item.supporter_id === "and_you";
|
||||||
|
|
||||||
|
const random_delay = `${randomMinMax(0.1, 6).toFixed(1)}s`;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const file_name = is_and_you ? "and_you" : `supporter_${item.supporter_id}`;
|
||||||
|
const label_img_src = getSupportersLabelsPath(file_name);
|
||||||
|
const icon_img_src = getSupportersIconsPath(`supporter_icon_${item.supporter_icon_id}`);
|
||||||
|
|
||||||
|
const supporter_label_component_classname = clsx(styles.supporter_label_component, {
|
||||||
|
[styles.is_icon_plan]: is_icon_plan,
|
||||||
|
});
|
||||||
|
|
||||||
|
const supporterLabelComponent = () => (
|
||||||
|
<div className={supporter_label_component_classname}>
|
||||||
|
{is_icon_plan && (
|
||||||
|
<div className={styles.supporter_icon_wrapper}>
|
||||||
|
{is_default_icon ? (
|
||||||
|
<img
|
||||||
|
className={styles.default_chato_expression_image}
|
||||||
|
src={chatoExpressions[index]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img className={styles.supporter_icon} src={icon_img_src} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<img className={styles.supporter_label_image} src={label_img_src} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const supporter_image_wrapper_classname = clsx(styles.supporter_image_wrapper, {
|
||||||
|
[styles.mogu_image]: target_plan === "もぐもぐ_2000",
|
||||||
|
});
|
||||||
|
|
||||||
|
return is_and_you ? (
|
||||||
|
<a href="#support_us_container" key={item.supporter_id}>
|
||||||
|
<div className={styles.supporter_image_container}>
|
||||||
|
<div
|
||||||
|
className={supporter_image_wrapper_classname}
|
||||||
|
style={{ "--delay": random_delay }}
|
||||||
|
>
|
||||||
|
<img className={styles.supporter_image} src={img_src} />
|
||||||
|
{supporterLabelComponent()}
|
||||||
|
<AndYouIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
): img_src ? (
|
||||||
|
<div key={item.supporter_id} className={styles.supporter_image_container}>
|
||||||
|
<div
|
||||||
|
className={supporter_image_wrapper_classname}
|
||||||
|
style={{ "--delay": random_delay }}
|
||||||
|
>
|
||||||
|
<img className={styles.supporter_image} src={img_src} />
|
||||||
|
{supporterLabelComponent()}
|
||||||
|
</div>
|
||||||
|
<SupporterPeriodContainer settings={item}/>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
shuffleSupporters();
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
shuffleSupporters();
|
||||||
|
}, SHUFFLE_INTERVAL_TIME);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.supporters_wrapper}>{renderImages()}</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const AndYouIcon = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.and_you_container}>
|
||||||
|
<div className={styles.and_you_1}></div>
|
||||||
|
<div className={styles.and_you_2}></div>
|
||||||
|
</div>
|
||||||
|
<p className={styles.and_you_fanbox_link_text}>
|
||||||
|
FANBOX Ko-fi Patreon
|
||||||
|
</p>
|
||||||
|
<ArrowLeftSvg className={styles.arrow_left_svg} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SupporterPeriodContainer = ({settings}) => {
|
||||||
|
|
||||||
|
const period_data = extractKeys(settings, calc_support_period);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.supporter_period_container}>
|
||||||
|
{Object.entries(period_data).map(([key, item], index) => {
|
||||||
|
if (item === "") return null;
|
||||||
|
const class_name = clsx(styles.period_box, {
|
||||||
|
[styles.mogu_bar]: item === "もぐもぐ_2000",
|
||||||
|
[styles.mochi_bar]: item === "もちもち_1000",
|
||||||
|
[styles.fuwa_bar]: item === "ふわふわ_500",
|
||||||
|
[styles.basic_bar]: item === "Basic_300",
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div key={index} className={class_name}></div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const extractKeys = (data, keys_to_extract) => {
|
||||||
|
const result = {};
|
||||||
|
for (const key of keys_to_extract) {
|
||||||
|
if (key in data) {
|
||||||
|
result[key] = data[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
.supporters_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: 1.8rem;
|
||||||
|
row-gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_container {
|
||||||
|
position: relative;
|
||||||
|
width: 18rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_wrapper {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_label_component {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.6rem;
|
||||||
|
&.is_icon_plan {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
top: 0.4rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_label_image {
|
||||||
|
height: 2rem;
|
||||||
|
// margin-top: 0.2rem;
|
||||||
|
&.small {
|
||||||
|
height: 1.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_icon_wrapper {
|
||||||
|
height: 4rem;
|
||||||
|
aspect-ratio: 1 /1;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_icon {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default_chato_expression_image {
|
||||||
|
position: absolute;
|
||||||
|
top: 52%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) rotate(10deg);
|
||||||
|
width: 2.8rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mogu_image {
|
||||||
|
position: relative;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -200%;
|
||||||
|
left: -200%;
|
||||||
|
width: 300%;
|
||||||
|
height: 300%;
|
||||||
|
background: linear-gradient(45deg, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0) 70%);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
animation: shine 2.5s infinite;
|
||||||
|
filter: blur(0.4rem);
|
||||||
|
animation-delay: var(--delay, 0s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shine {
|
||||||
|
0% {
|
||||||
|
top: -200%;
|
||||||
|
left: -200%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
top: 200%;
|
||||||
|
left: 200%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.and_you_container {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.and_you_1, .and_you_2 {
|
||||||
|
width: 2.2rem;
|
||||||
|
height: 0.2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--dark_400_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.and_you_2 {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_container {
|
||||||
|
&:hover .and_you_container {
|
||||||
|
top: 36%;
|
||||||
|
transform: translate(-50%, -50%) rotate(180deg);
|
||||||
|
animation: disappear 0.3s forwards;
|
||||||
|
}
|
||||||
|
&:hover .and_you_fanbox_link_text {
|
||||||
|
top: 74%;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&:hover .arrow_left_svg {
|
||||||
|
opacity: 0;
|
||||||
|
animation: arrow_up_down 0.3s forwards;
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_container.and_you_image {
|
||||||
|
cursor: pointer;
|
||||||
|
&:active {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.and_you_fanbox_link_text {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--dark_400_color);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow_left_svg {
|
||||||
|
position: absolute;
|
||||||
|
top: 60%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) rotate(90deg);
|
||||||
|
color: var(--dark_400_color);
|
||||||
|
width: 2rem;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes arrow_up_down {
|
||||||
|
0% {
|
||||||
|
top: 60%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
top: 36%;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes disappear {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.supporter_period_container {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.2rem;
|
||||||
|
padding-left: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period_box {
|
||||||
|
width: 1.8rem;
|
||||||
|
height: 0.3rem;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
&.mogu_bar {
|
||||||
|
background-color: var(--dark_basic_text_color);
|
||||||
|
}
|
||||||
|
&.mochi_bar {
|
||||||
|
background-color: var(--received_300_color);
|
||||||
|
}
|
||||||
|
&.fuwa_bar {
|
||||||
|
background-color: #5788a2;
|
||||||
|
}
|
||||||
|
&.basic_bar {
|
||||||
|
background-color: var(--dark_800_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"supporter_id": 1,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "Basic_300",
|
||||||
|
"2024-11": "Basic_300",
|
||||||
|
"2024-12": "Basic_300",
|
||||||
|
"2025-01": "Basic_300",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 2,
|
||||||
|
"supporter_icon_id": 8,
|
||||||
|
"2024-10": "もぐもぐ_2000",
|
||||||
|
"2024-11": "もぐもぐ_2000",
|
||||||
|
"2024-12": "もぐもぐ_2000",
|
||||||
|
"2025-01": "もぐもぐ_2000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 3,
|
||||||
|
"supporter_icon_id": 4,
|
||||||
|
"2024-10": "もちもち_1000",
|
||||||
|
"2024-11": "もちもち_1000",
|
||||||
|
"2024-12": "もちもち_1000",
|
||||||
|
"2025-01": "もちもち_1000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 4,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "もぐもぐ_2000",
|
||||||
|
"2024-11": "もぐもぐ_2000",
|
||||||
|
"2024-12": "もぐもぐ_2000",
|
||||||
|
"2025-01": "もぐもぐ_2000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 5,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "ふわふわ_500",
|
||||||
|
"2024-11": "ふわふわ_500",
|
||||||
|
"2024-12": "ふわふわ_500",
|
||||||
|
"2025-01": "Basic_300",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 6,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "ふわふわ_500",
|
||||||
|
"2024-11": "ふわふわ_500",
|
||||||
|
"2024-12": "ふわふわ_500",
|
||||||
|
"2025-01": "ふわふわ_500",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 7,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "ふわふわ_500",
|
||||||
|
"2024-11": "ふわふわ_500",
|
||||||
|
"2024-12": "ふわふわ_500",
|
||||||
|
"2025-01": "ふわふわ_500",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 8,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "Basic_300",
|
||||||
|
"2024-11": "Basic_300",
|
||||||
|
"2024-12": "Basic_300",
|
||||||
|
"2025-01": "",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 9,
|
||||||
|
"supporter_icon_id": 6,
|
||||||
|
"2024-10": "もぐもぐ_2000",
|
||||||
|
"2024-11": "もぐもぐ_2000",
|
||||||
|
"2024-12": "もぐもぐ_2000",
|
||||||
|
"2025-01": "もぐもぐ_2000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 10,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "Basic_300",
|
||||||
|
"2024-11": "Basic_300",
|
||||||
|
"2024-12": "Basic_300",
|
||||||
|
"2025-01": "Basic_300",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 11,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "もぐもぐ_2000",
|
||||||
|
"2024-11": "もぐもぐ_2000",
|
||||||
|
"2024-12": "もぐもぐ_2000",
|
||||||
|
"2025-01": "もぐもぐ_2000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 12,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "もぐもぐ_2000",
|
||||||
|
"2024-11": "もぐもぐ_2000",
|
||||||
|
"2024-12": "もぐもぐ_2000",
|
||||||
|
"2025-01": "もぐもぐ_2000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 13,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "Basic_300",
|
||||||
|
"2024-11": "Basic_300",
|
||||||
|
"2024-12": "Basic_300",
|
||||||
|
"2025-01": "Basic_300",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 14,
|
||||||
|
"supporter_icon_id": 2,
|
||||||
|
"2024-10": "もちもち_1000",
|
||||||
|
"2024-11": "もちもち_1000",
|
||||||
|
"2024-12": "もちもち_1000",
|
||||||
|
"2025-01": "もちもち_1000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 15,
|
||||||
|
"supporter_icon_id": 5,
|
||||||
|
"2024-10": "もぐもぐ_2000",
|
||||||
|
"2024-11": "もぐもぐ_2000",
|
||||||
|
"2024-12": "もぐもぐ_2000",
|
||||||
|
"2025-01": "もぐもぐ_2000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 16,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "もぐもぐ_2000",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 17,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "もちもち_1000",
|
||||||
|
"2024-11": "もちもち_1000",
|
||||||
|
"2024-12": "もちもち_1000",
|
||||||
|
"2025-01": "もちもち_1000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 18,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "Basic_300",
|
||||||
|
"2024-11": "Basic_300",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 19,
|
||||||
|
"supporter_icon_id": 1,
|
||||||
|
"2024-10": "もぐもぐ_2000",
|
||||||
|
"2024-11": "もぐもぐ_2000",
|
||||||
|
"2024-12": "もぐもぐ_2000",
|
||||||
|
"2025-01": "もぐもぐ_2000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 20,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "Basic_300",
|
||||||
|
"2024-11": "Basic_300",
|
||||||
|
"2024-12": "Basic_300",
|
||||||
|
"2025-01": "ふわふわ_500",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 21,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "ふわふわ_500",
|
||||||
|
"2024-12": "ふわふわ_500",
|
||||||
|
"2025-01": "ふわふわ_500",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 22,
|
||||||
|
"supporter_icon_id": 3,
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "もぐもぐ_2000",
|
||||||
|
"2025-01": "もぐもぐ_2000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 23,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "ふわふわ_500",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 24,
|
||||||
|
"supporter_icon_id": 7,
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "もぐもぐ_2000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 25,
|
||||||
|
"supporter_icon_id": 9,
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "もぐもぐ_2000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 26,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "もちもち_1000",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 27,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "Basic_300",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 28,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "Basic_300",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 29,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": 30,
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"supporter_id": "",
|
||||||
|
"supporter_icon_id": "",
|
||||||
|
"2024-10": "",
|
||||||
|
"2024-11": "",
|
||||||
|
"2024-12": "",
|
||||||
|
"2025-01": "",
|
||||||
|
"2025-02": "",
|
||||||
|
"2025-03": "",
|
||||||
|
"2025-04": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
Before Width: | Height: | Size: 4.0 MiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
BIN
src-ui/assets/supporters/fanbox_logo.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
src-ui/assets/supporters/kofi_logo.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 8.2 KiB |
BIN
src-ui/assets/supporters/patreon_1600x400px.png
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
src-ui/assets/supporters/patreon_logo.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src-ui/assets/supporters/supporter_cards/basic_card.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src-ui/assets/supporters/supporter_cards/fuwa_card.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src-ui/assets/supporters/supporter_cards/mochi_card.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src-ui/assets/supporters/supporter_cards/mogu_card.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src-ui/assets/supporters/supporters_icons/supporter_icon_1.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
src-ui/assets/supporters/supporters_icons/supporter_icon_2.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src-ui/assets/supporters/supporters_icons/supporter_icon_3.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src-ui/assets/supporters/supporters_icons/supporter_icon_4.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
src-ui/assets/supporters/supporters_icons/supporter_icon_5.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
src-ui/assets/supporters/supporters_icons/supporter_icon_6.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src-ui/assets/supporters/supporters_icons/supporter_icon_7.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src-ui/assets/supporters/supporters_icons/supporter_icon_8.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
src-ui/assets/supporters/supporters_icons/supporter_icon_9.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
BIN
src-ui/assets/supporters/supporters_labels/and_you.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_1.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_10.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_11.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_12.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_13.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_14.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_15.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_16.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_17.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_18.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_19.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_2.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_20.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_21.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_22.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_23.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_24.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_25.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_26.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_3.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_4.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_5.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_6.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_7.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_8.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
src-ui/assets/supporters/supporters_labels/supporter_9.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -31,6 +31,19 @@ export const randomIntMinMax = (min, max) => {
|
|||||||
return int;
|
return int;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const randomMinMax = (min, max) => {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shuffleArray = (array) => {
|
||||||
|
const new_array = [...array];
|
||||||
|
for (let i = new_array.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[new_array[i], new_array[j]] = [new_array[j], new_array[i]];
|
||||||
|
}
|
||||||
|
return new_array;
|
||||||
|
};
|
||||||
|
|
||||||
export const updateLabelsById = (data_array, updates) => {
|
export const updateLabelsById = (data_array, updates) => {
|
||||||
return data_array.map(item => {
|
return data_array.map(item => {
|
||||||
const update = updates.find(update_item => update_item.id === item.id);
|
const update = updates.find(update_item => update_item.id === item.id);
|
||||||
|
|||||||