@@ -87,20 +64,121 @@ const SupportUsContainer = () => {
);
};
+const getRandomImage = (images) => {
+ const random_index = Math.floor(Math.random() * images.length);
+ return images[random_index];
+};
+
export const SupportersContainer = () => {
- const renderImages = (image_list, class_name) => {
- return image_list.map((file_name) => {
+
+ return (
+
+

+
{`VRCT3.0のアップデートに向けて、めちゃ大変な開発を支えてくれた "Early Supporters" です。\nThey are the 'Early Supporters' who supported us through the incredibly challenging development toward the VRCT3.0 update.`}
+
+
+
+
{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(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!`}
+
+ );
+};
+
+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 (
+
+ );
+};
+
+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((default_num) => file_name.endsWith(default_num));
- const chato_expression_src = is_default_icon
- ? getChatoImageByFileName(getRandomImage(chato_expressions_filenames))
- : null;
+ 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 ? (

@@ -110,22 +188,37 @@ export const SupportersContainer = () => {
src={chato_expression_src}
/>
)}
+ {options.is_and_you_icon ?
: null}
) : null;
});
};
return (
-
-

-
{`VRCT3.0のアップデートに向けて、むちゃ大変な開発を支えてくれた "Early Supporters" です。\nThey are the 'Early Supporters' who supported us through the incredibly challenging development toward the VRCT3.0 update.`}
-
- {renderImages(mogu_images, `${styles.mogu_image}`)}
- {renderImages(mochi_images)}
- {renderImages(fuwa_images)}
- {renderImages(basic_images)}
- {renderImages(former_images)}
-
+
);
};
+
+const AndYouIcon = () => {
+ return (
+ <>
+
+
+ FANBOX
+
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss b/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss
index bd556503..4288b653 100644
--- a/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss
+++ b/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss
@@ -128,4 +128,78 @@
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%;
+ }
}
\ No newline at end of file
diff --git a/src-ui/assets/supporters/supporters_images/supporter_23.png b/src-ui/assets/supporters/supporters_images/supporter_23.png
new file mode 100644
index 00000000..207be07e
Binary files /dev/null and b/src-ui/assets/supporters/supporters_images/supporter_23.png differ
diff --git a/src-ui/logics/configs/index.js b/src-ui/logics/configs/index.js
index 646d24c1..b8426255 100644
--- a/src-ui/logics/configs/index.js
+++ b/src-ui/logics/configs/index.js
@@ -54,4 +54,5 @@ export { useSendTextToOverlay } from "./vr/useSendTextToOverlay";
export { useOscIpAddress } from "./advanced_settings/useOscIpAddress";
export { useOscPort } from "./advanced_settings/useOscPort";
+export { useSettingBoxScrollPosition } from "./useSettingBoxScrollPosition";
export { useSoftwareVersion } from "./useSoftwareVersion";
\ No newline at end of file
diff --git a/src-ui/logics/configs/useSettingBoxScrollPosition.js b/src-ui/logics/configs/useSettingBoxScrollPosition.js
new file mode 100644
index 00000000..c0a19b98
--- /dev/null
+++ b/src-ui/logics/configs/useSettingBoxScrollPosition.js
@@ -0,0 +1,28 @@
+import { store, useStore_SettingBoxScrollPosition } from "@store";
+
+export const useSettingBoxScrollPosition = () => {
+ const { currentSettingBoxScrollPosition, updateSettingBoxScrollPosition, pendingSettingBoxScrollPosition } = useStore_SettingBoxScrollPosition();
+
+ const saveScrollPosition = () => {
+ if (store.setting_box_scroll_container.current) {
+ updateSettingBoxScrollPosition(store.setting_box_scroll_container.current.scrollTop);
+ }
+ };
+ const restoreScrollPosition = () => {
+
+ if (store.setting_box_scroll_container.current) {
+ updateSettingBoxScrollPosition((pre) => {
+ store.setting_box_scroll_container.current.scrollTop = pre.data;
+ return pre.data;
+ })
+ }
+
+ };
+
+ return {
+ saveScrollPosition,
+ restoreScrollPosition,
+ currentSettingBoxScrollPosition,
+ updateSettingBoxScrollPosition,
+ };
+};
\ No newline at end of file
diff --git a/src-ui/store.js b/src-ui/store.js
index a7950a2a..19d81081 100644
--- a/src-ui/store.js
+++ b/src-ui/store.js
@@ -16,6 +16,7 @@ import {
export const store = {
backend_subprocess: null,
config_page: null,
+ setting_box_scroll_container: null,
log_box_ref: null,
is_applied_init_message_box_height: false,
};
@@ -160,6 +161,7 @@ export const { atomInstance: Atom_IsOpenedLanguageSelector, useHook: useStore_Is
// Common
export const { atomInstance: Atom_SoftwareVersion, useHook: useStore_SoftwareVersion } = createAtomWithHook("-", "SoftwareVersion");
export const { atomInstance: Atom_SelectedConfigTabId, useHook: useStore_SelectedConfigTabId } = createAtomWithHook("device", "SelectedConfigTabId");
+export const { atomInstance: Atom_SettingBoxScrollPosition, useHook: useStore_SettingBoxScrollPosition } = createAtomWithHook(0, "SettingBoxScrollPosition");
// Designs
export const { atomInstance: Atom_IsOpenedDropdownMenu, useHook: useStore_IsOpenedDropdownMenu } = createAtomWithHook("", "IsOpenedDropdownMenu");