Merge branch 'supporters_page' into develop

This commit is contained in:
Sakamoto Shiina
2025-02-03 17:15:08 +09:00
62 changed files with 451 additions and 576 deletions

View File

@@ -1,6 +1,6 @@
.scroll_container {
width: 100%;
overflow-y: auto;
overflow-y: scroll;
overflow-x: hidden;
}

View File

@@ -1,12 +1,22 @@
import styles from "./Supporters.module.scss";
import { SupportUsContainer } from "./support_us_container/SupportUsContainer";
import { SupportersContainer } from "./supporters_container/SupportersContainer";
import { useSupporters } from "@logics_configs";
import { useEffect } from "react";
export const Supporters = () => {
const { asyncFetchSupportersData } = useSupporters();
useEffect(() => {
asyncFetchSupportersData();
}, []);
return (
<div className={styles.container}>
<SupportUsContainer />
<SupportersContainer />
<div className={styles.supportersWrapper}>
<SupportersContainer />
</div>
</div>
);
};

View File

@@ -5,4 +5,17 @@
justify-content: center;
align-items: center;
width: 100%;
}
.supportersWrapper {
opacity: 0;
animation: fadeIn 0.8s ease-in-out 1.6s forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

View File

@@ -1,3 +1,44 @@
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
@keyframes revealTopImg {
0% {
clip-path: inset(0 50% 0 50%);
opacity: 0;
}
100% {
clip-path: inset(0 0 0 0);
opacity: 1;
}
}
@keyframes bounceIn {
0% {
transform: translateY(100%);
opacity: 0;
}
60% {
transform: translateY(-10%);
opacity: 1;
}
80% {
transform: translateY(10%);
opacity: 1;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes expandWidth {
0% {
transform: scaleX(0);
}
100% {
transform: scaleX(1);
}
}
.support_us_container {
display: flex;
flex-direction: column;
@@ -6,39 +47,56 @@
gap: 1.4rem;
width: 100%;
}
.support_buttons_wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 2rem;
gap: 1.2rem;
}
.top_img {
width: 100%;
animation: revealTopImg 0.8s ease forwards;
}
.lines_container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
gap: 3.6rem;
}
.line_basic, .line_fuwa, .line_mochi, .line_mogu {
.line_basic,
.line_fuwa,
.line_mochi,
.line_mogu {
width: 8.6rem;
height: 0.2rem;
transform: scaleX(0);
transform-origin: left center;
}
.line_basic {
background-color: var(--dark_800_color);
animation: expandWidth 1s $progress_ease 0.4s forwards;
}
.line_fuwa {
background-color: #5788a2;
animation: expandWidth 1s $progress_ease 0.6s forwards;
}
.line_mochi {
background-color: var(--received_300_color);
animation: expandWidth 1s $progress_ease 0.8s forwards;
}
.line_mogu {
background-color: var(--dark_basic_text_color);
animation: expandWidth 1s $progress_ease 1s forwards;
}
.support_us_button_wrapper {
@@ -52,8 +110,21 @@
.support_button {
position: relative;
padding: 1.2rem 1.6rem;
opacity: 0;
transform: translateY(100%);
}
.support_button:nth-child(1) {
animation: bounceIn 0.4s ease-out 1.2s forwards;
}
.support_button:nth-child(2) {
animation: bounceIn 0.4s ease-out 1.4s forwards;
}
.support_button:nth-child(3) {
animation: bounceIn 0.4s ease-out 1.6s forwards;
}
.support_img {
&.fanbox_logo {
height: 1.8rem;
@@ -64,8 +135,6 @@
}
.spiral_top::before,
.spiral_top::after,
.spiral_bottom::before,
@@ -74,6 +143,7 @@
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);
@@ -116,20 +186,17 @@
height: 0;
}
.support_button:hover
.spiral_top::before {
.support_button:hover .spiral_top::before {
height: 100%;
}
.support_button:hover
.spiral_top::after {
.support_button:hover .spiral_top::after {
width: 100%;
}
.support_button:hover
.spiral_bottom::before {
.support_button:hover .spiral_bottom::before {
width: 100%;
}
.support_button:hover
.spiral_bottom::after {
.support_button:hover .spiral_bottom::after {
height: 100%;
}
@@ -141,10 +208,12 @@
flex-direction: column;
gap: 1rem;
}
.vrct_supporters_title {
height: 6rem;
}
.vrct_supporters_desc {
font-size: 1.4rem;
text-align: start;
}
}

View File

@@ -1,40 +1,26 @@
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;
import { useSupporters } from "@logics_configs";
import { supporters_images_url } from "@ui_configs";
import vrct_supporters_title from "@images/supporters/vrct_supporters_title.png";
export const SupportersContainer = () => {
const { currentSupportersData } = useSupporters();
if (currentSupportersData.state === "error")
return <div>Failed to retrieve data.</div>;
if (currentSupportersData.state === "pending" || currentSupportersData.data === null)
return <div>Loading...</div>;
return (
<div className={styles.supporters_container}>
<img className={styles.vrct_supporters_title} src={vrct_supporters_title} />
<ProgressBar />
<div className={styles.vrct_supporters_title_wrapper}>
<img className={styles.vrct_supporters_title} src={vrct_supporters_title}/>
<img className={styles.calc_period} src={`${supporters_images_url}/calc_period_label.png`}/>
</div>
<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,
})}
/>
);
};

View File

@@ -5,22 +5,23 @@
flex-direction: column;
gap: 1rem;
}
.vrct_supporters_title_wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 0.2rem;
}
.vrct_supporters_title {
height: 6rem;
height: 4.2rem;
}
.calc_period {
height: 1.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%;
}
}

View File

@@ -6,222 +6,162 @@ import { shuffleArray, randomIntMinMax, randomMinMax } from "@utils";
import {
useSettingBoxScrollPosition,
} from "@logics_configs"
useSupporters,
} 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"];
import { supporters_images_url } from "@ui_configs";
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 }),
supporter_cards: `${supporters_images_url}/supporter_cards/`,
chato_expressions: `${supporters_images_url}/chato_expressions/`,
supporters_labels: `${supporters_images_url}/supporters_labels/`,
supporters_icons: `${supporters_images_url}/supporters_icons/`,
};
const getSupporterCard = (plan_name) => {
const card_map = {
"もぐもぐ_2000": "mogu_card",
"もちもち_1000": "mochi_card",
"ふわふわ_500": "fuwa_card",
"Basic_300": "basic_card",
"mogu_2000": "mogu_card",
"mochi_1000": "mochi_card",
"fuwa_500": "fuwa_card",
"basic_300": "basic_card",
};
return getImagePath(image_sets.supporter_cards, card_map[plan_name] || "basic_card");
if (!card_map[plan_name]) return `${image_sets.supporter_cards}basic_card.png`;
return `${image_sets.supporter_cards}${card_map[plan_name]}.png`;
};
const getChatoExpressionsPath = (file_name) =>
getImagePath(image_sets.chato_expressions, file_name);
const getChatoExpressionsPath = (file_name) => `${image_sets.chato_expressions}${file_name}.png`;
const getSupportersLabelsPath = (file_name) => `${image_sets.supporters_labels}${file_name}.png`;
const getSupportersIconsPath = (file_name) => `${image_sets.supporters_icons}${file_name}.png`;
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();
const { currentSupportersData } = useSupporters();
let credit_pending_count = 0;
const filtered_data = json_data.filter((supporter) => {
if (!supporter.supporter_id) return false;
const [json_data, setJsonData] = useState();
const [supportersData, setSupportersData] = useState([]);
const [chatoExpressions, setChatoExpressions] = useState([]);
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,
]);
useEffect(() => {
setJsonData(currentSupportersData.data);
}, [currentSupportersData.data]);
const [chatoExpressions, setChatoExpressions] = useState(() =>
supportersData.map(() =>
getChatoExpressionsPath(`chato_expression_${randomIntMinMax(1, chato_ex_count)}`)
)
);
const supporters_settings = currentSupportersData.data.supporters_settings;
const calc_support_period = supporters_settings.calc_support_period;
const target_supporting_month = calc_support_period.at(-1);
const chato_ex_count = supporters_settings.chato_ex_count;
const last_updated_local_date = new Date(supporters_settings.last_updated_utc_date)?.toString();
const recalcAndUpdateSupporters = useCallback(() => {
if (!json_data) return;
let credit_pending_count = 0;
const newGroupedData = {
"mogu_2000": [],
"mochi_1000": [],
"fuwa_500": [],
"basic_300": [],
"empty": [],
"and_you": [],
};
const filtered_data = json_data.supporters_data.filter((supporter) => {
if (!supporter.supporter_id) return false;
const months = Object.keys(supporter).filter((key) => calc_support_period.includes(key));
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) =>
["fuwa_500", "mochi_1000", "mogu_2000"].includes(supporter[month])
);
if (basic_300_months.length === 1 && !has_special_plan) {
credit_pending_count++;
return false;
}
return true;
});
filtered_data.forEach((supporter) => {
const value = supporter[target_supporting_month] || "empty";
if (newGroupedData[value]) {
newGroupedData[value].push(supporter);
} else {
newGroupedData["empty"].push(supporter);
}
});
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"]),
...shuffleArray(newGroupedData["mogu_2000"]),
...shuffleArray(newGroupedData["mochi_1000"]),
...shuffleArray(newGroupedData["fuwa_500"]),
...shuffleArray(newGroupedData["basic_300"]),
...shuffleArray(newGroupedData["empty"]),
and_you_data,
];
setSupportersData(newSupportersData);
setSupportersData(newSupportersData);
setChatoExpressions(
newSupportersData.map(() =>
getChatoExpressionsPath(`chato_expression_${randomIntMinMax(1, chato_ex_count)}`)
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;
});
};
}, [json_data]);
useEffect(() => {
recalcAndUpdateSupporters();
}, [json_data, recalcAndUpdateSupporters]);
const shuffleSupporters = useCallback(() => {
if (!json_data) return;
saveScrollPosition();
recalcAndUpdateSupporters();
setTimeout(() => restoreScrollPosition(), 0);
}, [json_data, recalcAndUpdateSupporters, saveScrollPosition, restoreScrollPosition]);
useEffect(() => {
shuffleSupporters();
const interval = setInterval(() => {
shuffleSupporters();
}, SHUFFLE_INTERVAL_TIME);
return () => clearInterval(interval);
}, []);
}, [shuffleSupporters]);
return (
<div className={styles.supporters_wrapper}>{renderImages()}</div>
<div className={styles.container}>
<ProgressBar />
<div className={styles.supporters_wrapper}>
<SupporterCardsComponent
supportersData={supportersData}
chatoExpressions={chatoExpressions}
target_supporting_month={target_supporting_month}
calc_support_period={calc_support_period}
/>
</div>
<p className={styles.last_updated_local_date}>{`Last updated date:\n${last_updated_local_date}`}</p>
<ProgressBar />
</div>
);
};
const AndYouIcon = () => {
return (
<>
@@ -237,29 +177,136 @@ const AndYouIcon = () => {
);
};
const SupporterPeriodContainer = ({settings}) => {
const SupporterCardsComponent = ({ supportersData, chatoExpressions, target_supporting_month, calc_support_period }) => {
return supportersData.map((item, index) => {
const target_plan = item[target_supporting_month];
const img_src = getSupporterCard(target_plan);
const is_and_you = item.supporter_id === "and_you";
const random_delay = `${randomMinMax(0.1, 6).toFixed(1)}s`;
const supporter_image_wrapper_classname = clsx(
styles.supporter_image_wrapper,
{
[styles.mogu_image]: target_plan === "mogu_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}
alt="supporter"
/>
<SupporterLabelComponent
target_plan={target_plan}
item={item}
chatoExpressions={chatoExpressions}
/>
<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}
alt="supporter"
/>
<SupporterLabelComponent
target_plan={target_plan}
item={item}
chato_src={chatoExpressions[index]}
index={index}
/>
</div>
<SupporterPeriodContainer settings={item} calc_support_period={calc_support_period}/>
</div>
) : null;
});
};
const SupporterLabelComponent = ({ item, target_plan, chato_src }) => {
const is_icon_plan = ["mogu_2000", "mochi_1000"].includes(
target_plan
);
const supporter_label_component_classname = clsx(
styles.supporter_label_component,
{
[styles.is_icon_plan]: is_icon_plan,
}
);
const is_and_you = item.supporter_id === "and_you";
const is_default_icon = item.supporter_icon_id === "";
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}`
);
return (
<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={chato_src}
alt="chato expression"
/>
) : (
<img
className={styles.supporter_icon}
src={icon_img_src}
alt="supporter icon"
/>
)}
</div>
)}
<img
className={styles.supporter_label_image}
src={label_img_src}
alt="supporter label"
/>
</div>
);
};
const SupporterPeriodContainer = ({ settings, calc_support_period }) => {
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",
[styles.mogu_bar]: item === "mogu_2000",
[styles.mochi_bar]: item === "mochi_1000",
[styles.fuwa_bar]: item === "fuwa_500",
[styles.basic_bar]: item === "basic_300",
});
return <div key={index} className={class_name}></div>
return <div key={index} className={class_name}></div>;
})}
</div>
);
};
const extractKeys = (data, keys_to_extract) => {
const result = {};
for (const key of keys_to_extract) {
@@ -268,4 +315,31 @@ const extractKeys = (data, keys_to_extract) => {
}
}
return result;
};
const ProgressBar = () => {
const [is_active, setIsActive] = useState(false);
useEffect(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
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,
})}
/>
);
};

View File

@@ -1,3 +1,11 @@
.container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 2rem;
}
.supporters_wrapper {
display: flex;
justify-content: center;
@@ -218,4 +226,22 @@
&.basic_bar {
background-color: var(--dark_800_color);
}
}
.progress_bar {
height: 0.2rem;
width: 0%;
&.progress_bar_active {
transition: width 20000ms linear;
background-color: var(--primary_400_color);
width: 100%;
}
}
.last_updated_local_date {
font-size: 1rem;
color: var(--dark_800_color);
width: 100%;
text-align: end;
}

View File

@@ -1,343 +0,0 @@
[
{
"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": ""
}
]

View File

@@ -1,4 +1,5 @@
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
// Duplicated
.container {
position: absolute;