);
});
+
+ const delayedShowTextbox = useDelayedVisibility(
+ GUIState.showTextBox && isText && !stageState.isDisableTextbox && stageState.enableFilm !== '',
+ );
+
return (
-
+ )}
+ >
);
};
diff --git a/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx
index f4abecc91..1840f68c6 100644
--- a/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx
+++ b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx
@@ -13,7 +13,6 @@ export default function StandardTextbox(props: ITextboxProps) {
isText,
isSafari,
isFirefox,
- fontSize,
miniAvatar,
isHasName,
showName,
@@ -140,13 +139,11 @@ export default function StandardTextbox(props: ITextboxProps) {
{textElementList}
diff --git a/packages/webgal/src/Stage/TextBox/textbox.module.scss b/packages/webgal/src/Stage/TextBox/textbox.module.scss
index c50816bc8..c0e82e21b 100644
--- a/packages/webgal/src/Stage/TextBox/textbox.module.scss
+++ b/packages/webgal/src/Stage/TextBox/textbox.module.scss
@@ -1,236 +1,169 @@
-.TextBox_EventHandler {
+.textbox_main {
position: absolute;
- width: 100%;
- height: 100%;
- z-index: 6;
- top: 0;
-}
-
-@mixin text_shadow_textElement {
- //text-shadow: 0 0 3px rgba(81,168,221,1);
+ display: flex;
+ justify-content: center;
+ align-items: flex-end;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 0 8vmin;
+ gap: 2vmin;
+ animation: textbox_main_show var(--ui-transition-duration) ease-out forwards;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: -50%;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(to top, rgb(23, 23, 31), rgba(23, 23, 31, 0));
+ opacity: var(--textbox-background-opacity);
+ }
}
-$height: 330px;
-
-.TextBox_Container {
- position: absolute;
- z-index: 6;
- bottom: 0;
- width: 100%;
- animation: showSoftly 0.7s ease-out forwards;
+@keyframes textbox_main_show {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
}
-.TextBox_main {
- z-index: 3;
- position: absolute;
- right: 25px;
- min-height: $height;
- max-height: $height;
- background-blend-mode: darken;
- border-radius: calc($height / 2) 20px 20px calc($height / 2);
- bottom: 20px;
- left: 275px;
- font-weight: bold;
- color: white;
- padding: 1em 50px 70px 200px;
- box-sizing: border-box;
- display: flex;
- flex-flow: column;
- align-items: flex-start;
- letter-spacing: 0.2em;
- transition: left 0.33s;
+.textbox_main_hide {
+ pointer-events: none;
+ animation: textbox_main_hide var(--ui-transition-duration) ease-out forwards;
}
-.TextBox_main_miniavatarOff {
- left: 25px;
+@keyframes textbox_main_hide {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
}
-.TextBox_Background {
- z-index: 2;
- background: linear-gradient(rgba(245, 247, 250, 1) 0%, rgba(189, 198, 222, 1) 100%);
+.textbox_mini_avatar {
+ max-width: 35vmin;
+ min-width: 35vmin;
+ width: 35vmin;
+ height: 0;
+ animation: textbox_mini_avatar_show 0.25s cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
}
-@keyframes showSoftly {
+@keyframes textbox_mini_avatar_show {
0% {
opacity: 0;
}
-
100% {
opacity: 1;
}
}
-//.TextBox_textElement {
-// opacity: 0;
-// animation: showSoftly 1000ms forwards;
-//}
+.textbox_mini_avatar_image {
+ transform: translateY(-100%);
+}
+.textbox_dialog {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ width: 100%;
+ max-width: 150vmin;
+}
-.TextBox_textElement_start {
- @include text_shadow_textElement;
- position: relative;
- animation: TextDelayShow 1000ms ease-out forwards;
- opacity: 0;
- display: inline-flex;
- align-content: space-between
+.textbox_name_container {
+ position: absolute;
+ transform: translateY(-120%);
+ display: flex;
+}
+
+.textbox_name_line {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.2vmin;
}
+.textbox_name {
+ display: flex;
+ color: rgb(112, 191, 255);
+ font-size: 4.5vmin;
+ font-weight: 700;
+}
-.outer {
- position: absolute;
- left:0;
- top:0;
- white-space: nowrap;
- //background-image: linear-gradient(rgba(255, 255, 255, 1) 0%, rgb(225, 237, 255) 100%);
- background-image: linear-gradient(#0B346E 0%,
- //#f5f7fa 45%,
- #141423 100%);
- //background: rgba(255, 255, 255, 1);
- background-clip: text;
- -webkit-background-clip: text;
- color: transparent;
- z-index: 2;
- display: inline-block; /* 与文本对齐 */
- vertical-align: middle; /* 确保注音整体的垂直居中 */
-}
-
-.inner {
- white-space: nowrap;
+.textbox_name_bellow {
position: absolute;
- left:0;
- top:0;
- -webkit-text-stroke: 0.1em rgba(255, 255, 255, 1);
- z-index: 1;
- //text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.75);
- display: inline-block; /* 内层元素同样行内块布局 */
- vertical-align: middle; /* 确保与外层的对齐一致 */
-}
-
-.zhanwei {
- color: transparent;
- white-space: nowrap;
- display: inline-block; /* 保持行内块布局 */
- vertical-align: baseline; /* 确保文本基线对齐 */
- position: relative; /* 保持相对定位 */
-}
-
-//
-//.TextBox_textElement_start::before{
-// animation: TextDelayShow 700ms ease-out forwards;
-// opacity: 0;
-// content:attr(data-text);
-// position: absolute;
-//}
-
-.TextBox_textElement_Settled {
+ -webkit-text-stroke: rgb(47, 47, 47);
+ -webkit-text-stroke-width: 0.7vmin;
+ text-shadow: rgb(47, 47, 47) 0 0.5vmin 1vmin;
+}
+
+.textbox_name_above {
position: relative;
- @include text_shadow_textElement;
- opacity: 1;
- display: inline-flex;
- align-content: space-between
-}
-
-//
-//.TextBox_textElement_Settled::before{
-// content:attr(data-text);
-// position: absolute;
-// opacity: 1;
-// -webkit-text-stroke: 1px red;
-// z-index: 1;
-//}
-
-.TextBox_showName {
- @include text_shadow_textElement;
- font-size: 85%;
- //border-bottom: 3px solid rgb(176, 176, 176);
- //min-width: 25%;
- padding: 0 2em 0 2em;
- //margin: 0 0 0 0;
- position: absolute;
- left: 150px;
- top: -68px;
- height: 80px;
- line-height: 68px;
- //display: flex;
- //align-items: center;
- // background: rgba(11, 52, 110, 0.9);
- border-radius: 40px;
- // border: 4px solid rgba(255, 255, 255, 0.75);
- // box-shadow: 3px 3px 10px rgba(100, 100, 100, 0.5);
- z-index: 3;
- border: 4px solid rgba(255, 255, 255, 0);
-}
-
-.TextBox_ShowName_Background {
- z-index: 2;
- background: rgba(11, 52, 110, 1);
- border: 4px solid rgba(255, 255, 255, 0.75);
- box-shadow: 3px 3px 10px rgba(100, 100, 100, 0.5);
-}
-
-@keyframes TextDelayShow {
+}
+
+.textbox_text_container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ min-height: 20vmin;
+ margin-bottom: 4vmin;
+ width: 100%;
+}
+
+.textbox_text_line {
+ display: flex;
+ align-items: flex-end;
+ flex-wrap: wrap;
+ gap: 0.2vmin;
+}
+
+.textbox_text {
+ display: flex;
+ color: rgb(233, 233, 233);
+ font-weight: 600;
+}
+
+.textbox_text_small {
+ font-size: 3.2vmin;
+}
+
+.textbox_text_medium {
+ font-size: 4vmin;
+}
+
+.textbox_text_large {
+ font-size: 5vmin;
+}
+
+.textbox_text_display {
+ display: flex;
+ opacity: 0;
+ animation: textbox_text_display_show var(--textbox-text-display-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards var(--textbox-text-display-delay);
+}
+
+@keyframes textbox_text_display_show {
0% {
opacity: 0;
}
-
100% {
opacity: 1;
}
}
-.miniAvatarContainer {
- position: absolute;
- height: 450px;
- width: 450px;
- bottom: 0;
- left: -250px;
- border-radius: 100% 0 0 100%;
- overflow: hidden;
-}
-
-.miniAvatarImg {
- max-height: 100%;
- max-width: 100%;
- position: absolute;
- bottom: 0;
- filter: drop-shadow(15px 0 3px rgba(0, 0, 0, 0.5));
-}
-
-.nameContainer {
- position: absolute;
- left: 2em;
- top: -3.5em;
+.textbox_text_display_settled {
+ opacity: 1;
}
-.outerName {
- position: absolute;
- left: 0;
- top: 0;
- //background-image: linear-gradient(rgba(255, 255, 255, 1) 0%, rgb(225, 237, 255) 100%);
- //background-image: linear-gradient(
- // #bfd8ff 0%,
- // //#f5f7fa 45%,
- // #bfbfc7 100%
- //);
- background: linear-gradient(150deg, rgb(255, 255, 255) 0%, rgb(255, 255, 255) 35%, rgb(165, 212, 228) 100%);
- //background: rgba(255, 255, 255, 1);
- background-clip: text;
- -webkit-background-clip: text;
- color: transparent;
- z-index: 2;
-}
-
-.innerName {
+.textbox_text_bellow {
position: absolute;
- left: 0;
- top: 0;
- //-webkit-text-stroke: 0.1em rgba(0, 0, 0, 0.25);
- //-webkit-text-stroke: 0.1em rgba(255, 255, 255, 1);
- z-index: 1;
- //text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.75);
+ -webkit-text-stroke: rgb(47, 47, 47);
+ -webkit-text-stroke-width: 0.7vmin;
+ text-shadow: rgb(47, 47, 47) 0 0.5vmin 1vmin;
}
-.text {
- //line-height: 2em;
- overflow: hidden;
+.textbox_text_above {
+ position: relative;
}
diff --git a/packages/webgal/src/Stage/TextBox/textboxFilm.module.scss b/packages/webgal/src/Stage/TextBox/textboxFilm.module.scss
index da3cac663..1d77f613b 100644
--- a/packages/webgal/src/Stage/TextBox/textboxFilm.module.scss
+++ b/packages/webgal/src/Stage/TextBox/textboxFilm.module.scss
@@ -1,41 +1,15 @@
-.TextBox_EventHandler {
+.textbox_film_main {
position: absolute;
- width: 100%;
- height: 100%;
- z-index: 6;
- top: 0;
-}
-
-.TextBox_main {
- font-family: "思源宋体", serif;
- font-style: italic;
- position: absolute;
- z-index: 6;
- width: 100%;
- height: 12%;
- //background: linear-gradient(transparent,
- // rgba(0, 0, 0, .25) 25%,
- // rgba(0, 0, 0, .35) 75%,
- // rgba(0, 0, 0, .6)),
- //linear-gradient(90deg, transparent 0,
- // rgba(0, 0, 0, .35) 25%,
- // rgba(0, 0, 0, .35) 75%,
- // transparent);
- background-color: black;
+ left: 0;
+ right: 0;
bottom: 0;
- color: white;
- //padding: 1em 18em 2em 18em;
- box-sizing: border-box;
- overflow: hidden;
- display: flex;
- flex-flow: column;
- align-items: center;
- animation: showSoftly 0.7s ease-out forwards;
- letter-spacing: 0.2em;
+ background-color: black;
justify-content: center;
+ opacity: 0;
+ animation: textbox_film_main var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
}
-@keyframes showSoftly {
+@keyframes textbox_film_main {
0% {
opacity: 0;
}
@@ -44,29 +18,55 @@
}
}
-.TextBox_textElement {
- opacity: 0;
- animation: showSoftly 1000ms forwards;
+.textbox_film_main_hide {
+ pointer-events: none;
+ animation: textbox_film_main_hide var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
}
-.TextBox_textElement_start {
- animation: TextDelayShow 700ms ease-out forwards;
- opacity: 0;
+@keyframes textbox_film_main_hide {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
}
-.TextBox_textElement_Settled {
- opacity: 1;
+.textbox_film_text_container {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: wrap;
+ width: 100%;
+ height: auto;
+ padding: 2vmin 8vmin;
+}
+
+.textbox_film_text {
+ display: flex;
+ color: rgb(255, 255, 255);
+ font-weight: 600;
+}
+
+.textbox_film_text_small {
+ font-size: 3.2vmin;
+}
+
+.textbox_film_text_medium {
+ font-size: 4vmin;
+}
+
+.textbox_film_text_large {
+ font-size: 5vmin;
}
-.TextBox_showName {
- font-size: 85%;
- border-bottom: 2px solid rgba(255, 255, 255, 0.3);
- min-width: 50%;
- padding: 0 0.2em 0.2em 0.3em;
- margin: 0 0 0.2em 0;
+.textbox_film_text_display {
+ animation: textbox_film_text_display_show var(--textbox-text-display-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards var(--textbox-text-display-delay);
+ opacity: 0;
}
-@keyframes TextDelayShow {
+@keyframes textbox_film_text_display_show {
0% {
opacity: 0;
}
@@ -75,17 +75,6 @@
}
}
-.miniAvatarContainer {
- position: absolute;
- height: 80%;
- width: 17%;
- bottom: 0;
- left: 0.5em;
-}
-
-.miniAvatarImg {
- max-height: 100%;
- max-width: 100%;
- position: absolute;
- bottom: 0;
-}
+.textbox_film_text_element_settled {
+ opacity: 1;
+}
\ No newline at end of file
diff --git a/packages/webgal/src/Stage/TextBox/types.ts b/packages/webgal/src/Stage/TextBox/types.ts
index 59e41e126..e5e6c4f63 100644
--- a/packages/webgal/src/Stage/TextBox/types.ts
+++ b/packages/webgal/src/Stage/TextBox/types.ts
@@ -1,4 +1,5 @@
import { EnhancedNode } from '@/Stage/TextBox/TextBox';
+import { textSize } from '@/store/userDataInterface';
export interface ITextboxProps {
textArray: EnhancedNode[][];
@@ -8,14 +9,12 @@ export interface ITextboxProps {
isText: boolean;
isSafari: boolean;
isFirefox: boolean;
- fontSize: string;
miniAvatar: string;
showName: EnhancedNode[][];
isHasName: boolean;
font: string;
textDuration: number;
- textSizeState: number;
- lineLimit: number;
+ textSizeState: textSize;
isUseStroke: boolean;
textboxOpacity: number;
}
diff --git a/packages/webgal/src/Stage/introContainer/IntroContainer.tsx b/packages/webgal/src/Stage/introContainer/IntroContainer.tsx
index 16b7dfb5b..5cdc18bae 100644
--- a/packages/webgal/src/Stage/introContainer/IntroContainer.tsx
+++ b/packages/webgal/src/Stage/introContainer/IntroContainer.tsx
@@ -1,5 +1,7 @@
-import styles from './introContainer.module.scss';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import styles from '@/Core/gameScripts/intro/intro.module.scss';
export default function IntroContainer() {
- return
;
+ const applyStyle = useApplyStyle('Stage/Intro/intro.scss');
+ return
;
}
diff --git a/packages/webgal/src/Stage/introContainer/introContainer.module.scss b/packages/webgal/src/Stage/introContainer/introContainer.module.scss
deleted file mode 100644
index 034ae238f..000000000
--- a/packages/webgal/src/Stage/introContainer/introContainer.module.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-.introContainer {
- box-sizing: border-box;
- position: absolute;
- z-index: 11;
- width: 100%;
- height: 100%;
- color: white;
- display: none;
- //z-index: 13;
-}
diff --git a/packages/webgal/src/Stage/stage.module.scss b/packages/webgal/src/Stage/stage.module.scss
index ffa0e6db5..a048dea4d 100644
--- a/packages/webgal/src/Stage/stage.module.scss
+++ b/packages/webgal/src/Stage/stage.module.scss
@@ -1,82 +1,127 @@
-.MainStage_main {
+@import '../Core/util/constants.scss';
+
+.stage_main {
width: 100%;
height: 100%;
position: absolute;
- z-index: 1;
- //animation: MainStage_showBgSoftly 100ms forwards;
- opacity: 1;
+ display: flex;
overflow: hidden;
+ align-items: center;
+ justify-content: center;
+ container-type: size;
}
-.MainStage_main_container {
- width: 100%;
- height: 100%;
+.stage_pixi_container {
position: absolute;
- overflow: hidden;
+ display: flex;
+ flex-grow: 1;
+ min-width: 0;
+ min-height: 0;
}
-.MainStage_bgContainer {
- top: 0;
+.stage_choose_container {
position: absolute;
- background-size: cover;
+ display: flex;
+ top: 0;
width: 100%;
height: 100%;
- z-index: 1;
- animation: MainStage_showBgSoftly 1s forwards ease-in-out;
+ pointer-events: none;
}
-.MainStage_bgContainer_Settled {
- top: 0;
+.stage_get_user_input_container {
position: absolute;
- background-size: cover;
+ display: flex;
+ top: 0;
width: 100%;
height: 100%;
- animation: MainStage_showBgSoftly 1ms forwards;
- z-index: 1;
+ pointer-events: none;
}
-.MainStage_oldBgContainer {
- background-size: cover;
- top: 0;
+.stage_video_container {
position: absolute;
+ display: flex;
+ top: 0;
width: 100%;
height: 100%;
- z-index: 0;
- animation: MainStage_oldBgFadeout 3s forwards;
+ pointer-events: none;
}
-.MainStage_oldBgContainer_Settled {
- background-size: cover;
- top: 0;
+.stage_intro_container {
position: absolute;
+ top: 0;
width: 100%;
height: 100%;
- opacity: 0;
+ pointer-events: none;
}
-@keyframes MainStage_showBgSoftly {
- 0% {
- opacity: 0.15;
- }
- 100% {
- opacity: 1;
- }
+.stage_full_screen_click {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ pointer-events: all;
}
-@keyframes MainStage_oldBgFadeout {
- 0% {
- opacity: 1;
- }
- 100% {
- opacity: 0;
- }
-}
+// .MainStage_main_container {
+// width: 100%;
+// height: 100%;
+// position: absolute;
+// overflow: hidden;
+// }
-.pixiContainer{
- position: absolute;
- z-index: 5;
-}
+// .MainStage_bgContainer {
+// top: 0;
+// position: absolute;
+// background-size: cover;
+// width: 100%;
+// height: 100%;
+// z-index: 1;
+// animation: MainStage_showBgSoftly 1s forwards ease-in-out;
+// }
-.chooseContainer{
- z-index: 8;
-}
+// .MainStage_bgContainer_Settled {
+// top: 0;
+// position: absolute;
+// background-size: cover;
+// width: 100%;
+// height: 100%;
+// animation: MainStage_showBgSoftly 1ms forwards;
+// z-index: 1;
+// }
+
+// .MainStage_oldBgContainer {
+// background-size: cover;
+// top: 0;
+// position: absolute;
+// width: 100%;
+// height: 100%;
+// z-index: 0;
+// animation: MainStage_oldBgFadeout 3s forwards;
+// }
+
+// .MainStage_oldBgContainer_Settled {
+// background-size: cover;
+// top: 0;
+// position: absolute;
+// width: 100%;
+// height: 100%;
+// opacity: 0;
+// }
+
+// @keyframes MainStage_showBgSoftly {
+// 0% {
+// opacity: 0.15;
+// }
+// 100% {
+// opacity: 1;
+// }
+// }
+
+// @keyframes MainStage_oldBgFadeout {
+// 0% {
+// opacity: 1;
+// }
+// 100% {
+// opacity: 0;
+// }
+// }
diff --git a/packages/webgal/src/UI/Backlog/backlog.module.scss b/packages/webgal/src/UI/Backlog/backlog.module.scss
deleted file mode 100644
index 7d3f9a6dd..000000000
--- a/packages/webgal/src/UI/Backlog/backlog.module.scss
+++ /dev/null
@@ -1,220 +0,0 @@
-.Backlog_main {
- font-family: "思源宋体", serif;
- position: absolute;
- top: 0;
- width: 100%;
- height: 100%;
- z-index: 10;
- background: rgba(0, 0, 0, 0.7);
- padding: 2em 0 2em 0;
- animation: backlog_soft_in 0.7s ease-out forwards;
- box-sizing: border-box;
-}
-
-.Backlog_main_out {
- font-family: "思源宋体", serif;
- position: absolute;
- top: 0;
- width: 100%;
- height: 100%;
- z-index: 10;
- background: rgba(0, 0, 0, 0.7);
- padding: 2em 0 2em 0;
- animation: backlog_soft_out 0.7s ease-out forwards;
- box-sizing: border-box;
-}
-
-// 把z-index置为负数 不然会挡住点击层
-.Backlog_main_out_IndexHide {
- z-index: -10;
-}
-
-// 暂时禁用滚动
-.Backlog_main_DisableScroll {
- overflow: hidden !important;
-}
-
-
-.backlog_top {
- padding: 0 0 0 1em;
- display: flex;
- height: 10%;
-}
-
-.backlog_top_icon {
- padding: 0.6em 0.6em 0 0.6em;
- border-radius: 1000px;
- transform: translate(0, -13px);
- cursor: pointer;
-}
-
-.backlog_top_icon:hover {
- background: rgba(255, 255, 255, 0.25);
- animation: backlog_icon_softin 0.25s ease-out forwards;
-}
-
-@keyframes backlog_icon_softin {
- 0% {
- background: rgba(255, 255, 255, 0);
- }
- 100% {
- background: rgba(255, 255, 255, 0.25);
- }
-}
-
-
-.backlog_title {
- height: 100%;
- line-height: 100%;
- font-size: 360%;
- font-weight: bold;
- color: transparent;
- background: linear-gradient(150deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 35%, rgba(165, 212, 228, 1) 100%);
- -webkit-background-clip: text;
-}
-
-.backlog_content {
- position: absolute;
- height: 80%;
- padding: 1em 10em 1em 10em;
- overflow: auto;
- display: flex;
- flex-flow: column-reverse;
- font-weight: normal;
- width: 100%;
- box-sizing: border-box;
-}
-
-
-.backlog_item {
- display: flex;
- color: white;
- font-size: 165%;
- opacity: 0;
- animation: backlog_item_in 0.5s ease-out forwards;
- margin: 1.25em 0 0 0;
- width: 100%;
-}
-
-.backlog_item_out {
- display: flex;
- color: white;
- font-size: 165%;
- opacity: 0;
- animation: backlog_item_out 0.5s ease-out forwards;
- margin: 1.25em 0 0 0;
- width: 100%;
-}
-
-.backlog_func_area {
- display: flex;
- flex-flow: row;
- align-items: flex-start;
- width: 30%;
- max-width: 30%;
- min-width: 30%;
-}
-
-.backlog_item_content_name {
- font-weight: bold;
- color: transparent;
- //background: linear-gradient(150deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 55%, rgb(210, 243, 255) 100%);
- background: linear-gradient(150deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 35%, rgba(165, 212, 228, 1) 100%);
- -webkit-background-clip: text;
- //width: 20%;
- margin: 0 0 0 auto;
- overflow-wrap: break-word;
- box-sizing: border-box;
- //background: rgba(255, 255, 255, 0.175);
- border-radius: 7px;
- padding: 0.2em 0.5em 0.2em 0.5em;
- font-size: 115%;
- width: 50%;
- text-align: left;
- //font-family: WebgalUI, serif;
- letter-spacing: 0.1em;
-}
-
-.backlog_item_content {
- //display: flex;
- font-size: 115%;
- width: 82.5%;
- box-sizing: border-box;
- padding: 0.2em 0 0 1em;
- //font-family: WebgalUI, serif;
- letter-spacing: 0.05em;
-}
-
-.backlog_item_button_list {
- display: flex;
- //padding: 0 2em 0 0.3em;
- flex-flow: row;
- align-items: flex-start;
- margin: 0.35em 0 0 0;
-}
-
-.backlog_item_button_element {
- cursor: pointer;
- padding: 0.01em 0.75em 0 0.75em;
- margin: 0 0 0 0.5em;
- background: rgba(255, 255, 255, 0.075);
- border-radius: 7px;
- display: flex;
- //border: 1px solid rgba(255, 255, 255, 0.15);
- //box-shadow: 0 0 15px rgba(255, 255, 255, 0.25);
-}
-
-.backlog_item_button_element:hover {
- background: rgba(255, 255, 255, 0.25);
- //border: 1px solid rgba(255, 255, 255, 0);
- //box-shadow: 0 0 15px rgba(255, 255, 255, 0.3);
-}
-
-
-.backlog_item_content_text {
- //width: 80%;
- box-sizing: border-box;
-}
-
-
-@keyframes backlog_soft_in {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
-}
-
-@keyframes backlog_soft_out {
- 0% {
- opacity: 1;
- }
- 100% {
- opacity: 0;
- }
-}
-
-@keyframes backlog_item_in {
- 0% {
- opacity: 0;
- transform: scale(1.05, 1.05) translate(-15px, 10px) rotateX(-5deg) rotateY(-5deg);
- //background-color: rgba(255, 255, 255, 0.2);
- }
- 100% {
- opacity: 1;
- transform: scale(1, 1) translate(0, 0);
- }
-}
-
-@keyframes backlog_item_out {
- 0% {
- opacity: 1;
- transform: scale(1, 1) translate(0, 0);
- }
- 100% {
- opacity: 0;
- transform: scale(1.05, 1.05) translate(-15px, 10px) rotateX(-5deg) rotateY(-5deg);
- background-color: rgba(255, 255, 255, 0.2);
- }
-}
diff --git a/packages/webgal/src/UI/BottomControlPanel/BottomControlPanel.tsx b/packages/webgal/src/UI/BottomControlPanel/BottomControlPanel.tsx
deleted file mode 100644
index d2df8848e..000000000
--- a/packages/webgal/src/UI/BottomControlPanel/BottomControlPanel.tsx
+++ /dev/null
@@ -1,351 +0,0 @@
-import { switchAuto } from '@/Core/controller/gamePlay/autoPlay';
-import { backToTitle } from '@/Core/controller/gamePlay/backToTitle';
-import { switchFast } from '@/Core/controller/gamePlay/fastSkip';
-import { loadGame } from '@/Core/controller/storage/loadGame';
-import { saveGame } from '@/Core/controller/storage/saveGame';
-import { showGlogalDialog, switchControls } from '@/UI/GlobalDialog/GlobalDialog';
-import { easyCompile } from '@/UI/Menu/SaveAndLoad/Save/Save';
-import useFullScreen from '@/hooks/useFullScreen';
-import useSoundEffect from '@/hooks/useSoundEffect';
-import useTrans from '@/hooks/useTrans';
-import { setMenuPanelTag, setVisibility } from '@/store/GUIReducer';
-import { componentsVisibility, MenuPanelTag } from '@/store/guiInterface';
-import { RootState } from '@/store/store';
-import {
- AlignTextLeftOne,
- DoubleDown,
- DoubleRight,
- DoubleUp,
- FolderOpen,
- FullScreen,
- Home,
- Lock,
- OffScreen,
- PlayOne,
- PreviewCloseOne,
- PreviewOpen,
- ReplayMusic,
- Save,
- SettingTwo,
- Unlock,
-} from '@icon-park/react';
-import { useTranslation } from 'react-i18next';
-import { useDispatch, useSelector } from 'react-redux';
-import styles from './bottomControlPanel.module.scss';
-
-export const BottomControlPanel = () => {
- const t = useTrans('gaming.');
- const strokeWidth = 2.5;
- const { i18n } = useTranslation();
- const { playSeEnter, playSeClick, playSeDialogOpen } = useSoundEffect();
- const lang = i18n.language;
- const isFr = lang === 'fr';
- let size = 42;
- let fontSize = '150%';
- if (isFr) {
- fontSize = '125%';
- size = 40;
- }
- const { isSupported: isFullscreenSupport, isFullScreen, toggle: toggleFullscreen } = useFullScreen();
- const GUIStore = useSelector((state: RootState) => state.GUI);
- const stageState = useSelector((state: RootState) => state.stage);
- const dispatch = useDispatch();
- const setComponentVisibility = (component: keyof componentsVisibility, visibility: boolean) => {
- dispatch(setVisibility({ component, visibility }));
- };
- const setMenuPanel = (menuPanel: MenuPanelTag) => {
- dispatch(setMenuPanelTag(menuPanel));
- };
-
- const saveData = useSelector((state: RootState) => state.saveData.saveData);
- let fastSlPreview = (
-
- );
- if (saveData[0]) {
- const data = saveData[0];
- fastSlPreview = (
-
-
-

-
-
-
{easyCompile(data.nowStageState.showName)}
-
{easyCompile(data.nowStageState.showText)}
-
-
- );
- }
-
- return (
- //
- <>
- {GUIStore.showTextBox && stageState.enableFilm === '' && (
-
- {GUIStore.showTextBox && (
-
{
- setComponentVisibility('showTextBox', false);
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.hide')}
-
- )}
- {!GUIStore.showTextBox && (
-
{
- setComponentVisibility('showTextBox', true);
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.show')}
-
- )}
-
{
- setComponentVisibility('showBacklog', true);
- setComponentVisibility('showTextBox', false);
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.backlog')}
-
-
{
- let VocalControl: any = document.getElementById('currentVocal');
- if (VocalControl !== null) {
- VocalControl.currentTime = 0;
- VocalControl.pause();
- VocalControl?.play();
- }
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.replay')}
-
-
{
- switchAuto();
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.auto')}
-
-
{
- switchFast();
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.forward')}
-
-
{
- saveGame(0);
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.quicklySave')}
- {fastSlPreview}
-
-
{
- loadGame(0);
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.quicklyLoad')}
- {fastSlPreview}
-
-
{
- setMenuPanel(MenuPanelTag.Save);
- setComponentVisibility('showMenuPanel', true);
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.save')}
-
-
{
- setMenuPanel(MenuPanelTag.Load);
- setComponentVisibility('showMenuPanel', true);
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.load')}
-
-
{
- setMenuPanel(MenuPanelTag.Option);
- setComponentVisibility('showMenuPanel', true);
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.options')}
-
-
{
- playSeDialogOpen();
- showGlogalDialog({
- title: t('buttons.titleTips'),
- leftText: t('$common.yes'),
- rightText: t('$common.no'),
- leftFunc: () => {
- backToTitle();
- },
- rightFunc: () => {},
- });
- }}
- onMouseEnter={playSeEnter}
- >
-
- {t('buttons.title')}
-
- {isFullscreenSupport && (
-
- {!isFullScreen && (
-
- )}
- {isFullScreen && (
-
- )}
- {t('buttons.fullscreen')}
-
- )}
-
{
- switchControls();
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
- {GUIStore.showControls ? (
-
- ) : (
-
- )}
-
-
- )}
- >
- //
- );
-};
diff --git a/packages/webgal/src/UI/BottomControlPanel/BottomControlPanelFilm.tsx b/packages/webgal/src/UI/BottomControlPanel/BottomControlPanelFilm.tsx
deleted file mode 100644
index b6ed7b2e4..000000000
--- a/packages/webgal/src/UI/BottomControlPanel/BottomControlPanelFilm.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import styles from './bottomControlPanelFilm.module.scss';
-import { switchAuto } from '@/Core/controller/gamePlay/autoPlay';
-import { switchFast } from '@/Core/controller/gamePlay/fastSkip';
-import { useDispatch, useSelector } from 'react-redux';
-import { RootState } from '@/store/store';
-import { setMenuPanelTag, setVisibility } from '@/store/GUIReducer';
-import { componentsVisibility, MenuPanelTag } from '@/store/guiInterface';
-import { backToTitle } from '@/Core/controller/gamePlay/backToTitle';
-import { useValue } from '@/hooks/useValue';
-import { HamburgerButton } from '@icon-park/react';
-
-export const BottomControlPanelFilm = () => {
- const showPanel = useValue(false);
- const stageState = useSelector((state: RootState) => state.stage);
- const dispatch = useDispatch();
- const setComponentVisibility = (component: keyof componentsVisibility, visibility: boolean) => {
- dispatch(setVisibility({ component, visibility }));
- };
- const setMenuPanel = (menuPanel: MenuPanelTag) => {
- dispatch(setMenuPanelTag(menuPanel));
- };
- return (
- <>
- {stageState.enableFilm !== '' && (
- <>
-
{
- showPanel.set(!showPanel.value);
- }}
- >
-
-
- {showPanel.value && (
-
- {
- setComponentVisibility('showBacklog', true);
- setComponentVisibility('showTextBox', false);
- showPanel.set(!showPanel.value);
- }}
- >
- 剧情回想 / BACKLOG
-
- {
- showPanel.set(!showPanel.value);
- let VocalControl: any = document.getElementById('currentVocal');
- if (VocalControl !== null) {
- VocalControl.currentTime = 0;
- VocalControl.pause();
- VocalControl?.play();
- }
- }}
- >
- 重播语音 / REPLAY VOICE
-
- {
- switchAuto();
- showPanel.set(!showPanel.value);
- }}
- >
- 自动模式 / AUTO
-
- {
- switchFast();
- showPanel.set(!showPanel.value);
- }}
- >
- 快进 / FAST
-
- {
- showPanel.set(!showPanel.value);
- setMenuPanel(MenuPanelTag.Save);
- setComponentVisibility('showMenuPanel', true);
- }}
- >
- 存档 / SAVE
-
- {
- showPanel.set(!showPanel.value);
- setMenuPanel(MenuPanelTag.Load);
- setComponentVisibility('showMenuPanel', true);
- }}
- >
- 读档 / LOAD
-
- {
- showPanel.set(!showPanel.value);
- setMenuPanel(MenuPanelTag.Option);
- setComponentVisibility('showMenuPanel', true);
- }}
- >
- 选项 / OPTIONS
-
- {
- showPanel.set(!showPanel.value);
- backToTitle();
- }}
- >
- 标题 / TITLE
-
-
- )}
- >
- )}
- >
- );
-};
diff --git a/packages/webgal/src/UI/BottomControlPanel/bottomControlPanel.module.scss b/packages/webgal/src/UI/BottomControlPanel/bottomControlPanel.module.scss
deleted file mode 100644
index c9c63aeba..000000000
--- a/packages/webgal/src/UI/BottomControlPanel/bottomControlPanel.module.scss
+++ /dev/null
@@ -1,128 +0,0 @@
-//.ToCenter {
-// display: flex;
-// justify-content: center;
-// position: absolute;
-// bottom: 0;
-// left: 0;
-// width: 100%;
-// z-index: 9;
-//}
-
-.main {
- //border-bottom: 2px solid rgba(255,255,255,0.25);
- position: absolute;
- bottom: 20px;
- //will-change: z-index;
- z-index: 9;
- display: flex;
- //background-image: linear-gradient(rgba(245, 247, 250, 0.7) 0%, rgba(195, 207, 226, 0.7) 100%);
- flex-flow: row;
- justify-content: center;
- align-items: center;
- //width: 100%;
- height: 70px;
- right: 20px;
- border-radius: 35px;
- //backdrop-filter: blur(5px);
- padding: 0.15em 1.75em 0.15em 1.75em;
- //background: linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.25) 100%);
- //border-radius: 10px;
- font-size: 80%;
-}
-
-.button {
- position: relative;
- top: 2px;
- padding: 0 0 0 0;
- filter: drop-shadow(1px 1px 5px rgba(0, 0, 0, 1));
-}
-
-.button_text {
- position: relative;
- bottom: 8px;
- color: white;
- text-shadow: 1px 1px 5px rgba(0, 0, 0, 1);
- padding-left: 3px;
-}
-
-.button_on {
- height: 100%;
- display: inline-block;
- font-size: 150%;
- padding: 0.25em 0.3em 0 0.15em;
- transition: background-color 0.5s;
- background: rgba(255, 255, 255, 0.3);
- border-radius: 4px;
- margin: 0 0.1em 0 0.1em;
-}
-
-.singleButton {
- //border-bottom: 1px solid rgba(255, 255, 255, 0.5);
- height: 100%;
- display: inline-block;
- color: white;
- font-size: 150%;
- padding: 0.3em 0.3em 0 0.15em;
- transition: background-color 0.5s;
- cursor: pointer;
- border-radius: 4px;
- margin: 0 0.1em 0 0.1em;
- position: relative;
-}
-
-.singleButton:hover, .singleButton_active {
- background-color: rgba(255, 255, 255, 0.3);
-}
-
-.fastSlPreview {
- position: absolute;
- top: -250px;
- right: 0;
- background: linear-gradient(315deg, rgba(253, 251, 251, 0.9) 0%, rgba(235, 237, 238, 0.85) 100%);
- width: 900px;
- height: 230px;
- color: #005caf;
- border-radius: 5px;
- display: none;
- animation: fastSlEnter 0.33s;
- transition: opacity 0.33s;
-}
-
-.fastsave:hover .fastSPreview {
- display: block;
-}
-
-.fastload:hover .fastLPreview {
- display: block;
-}
-
-@keyframes fastSlEnter {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
-}
-
-.slPreviewMain {
- padding: 0.5em 0.5em 0.5em 0.5em;
- display: flex;
- box-sizing: border-box;
- height: 100%;
- width: 100%;
-}
-
-.imgContainer {
- display: flex;
- overflow: hidden;
- border-radius: 5px;
- //outline: 4px solid #005caf;
- flex-shrink: 0;
- height: 100%;
-}
-
-.textContainer {
- overflow: hidden;
- padding: 0 0 0 0.5em;
-}
diff --git a/packages/webgal/src/UI/BottomControlPanel/bottomControlPanelFilm.module.scss b/packages/webgal/src/UI/BottomControlPanel/bottomControlPanelFilm.module.scss
deleted file mode 100644
index 541765e54..000000000
--- a/packages/webgal/src/UI/BottomControlPanel/bottomControlPanelFilm.module.scss
+++ /dev/null
@@ -1,54 +0,0 @@
-.tag {
- position: absolute;
- top: 2.5%;
- left: 2.5%;
- color: white;
- z-index: 10;
- padding: 10px 10px 5px 10px;
- border-radius: 100px;
- transition: background-color 0.33s;
-}
-
-.tag:hover {
- background-color: rgba(255, 255, 255, 0.5);
-}
-
-.container {
- color: white;
- position: absolute;
- top: 0;
- width: 100%;
- height: 100%;
- display: flex;
- flex-flow: column;
- background-color: rgba(0, 0, 0, 0.7);
- z-index: 9;
- padding: 7em 5em 5em 10em;
- opacity: 0;
- animation: showContainer 1s forwards;
- transition: background-color 0.33s;
-}
-
-.singleButton {
- padding: 0.5em 0 0.5em 0;
-}
-
-.button_text {
- font-family: "思源宋体", serif;
- font-size: 250%;
- letter-spacing: 0.07em;
- transition: text-shadow 0.33s;
-}
-
-.button_text:hover {
- text-shadow: 0 0 15px rgba(255, 255, 255, 1);
-}
-
-@keyframes showContainer {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
-}
diff --git a/packages/webgal/src/UI/DevPanel/DevPanel.tsx b/packages/webgal/src/UI/DevPanel/DevPanel.tsx
index 2fbb702a1..cf1c619bf 100644
--- a/packages/webgal/src/UI/DevPanel/DevPanel.tsx
+++ b/packages/webgal/src/UI/DevPanel/DevPanel.tsx
@@ -1,12 +1,24 @@
import styles from './devPanel.module.scss';
import { useValue } from '@/hooks/useValue';
import { getPixiSscreenshot } from '@/UI/DevPanel/devFunctions/getPixiSscreenshot';
-import { useEffect } from 'react';
+import { FC, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { RootState } from '@/store/store';
import { useTranslation } from 'react-i18next';
import { WebGAL } from '@/Core/WebGAL';
+import useLanguage from '@/hooks/useLanguage';
+import { language } from '@/config/language';
+
+type JSONValue = string | number | boolean | null | IJsonObject | JSONArray;
+interface IJsonObject {
+ [key: string]: JSONValue;
+}
+type JSONArray = JSONValue[];
+
+interface IJsonViewerProps {
+ jsonString: string;
+}
export default function DevPanel() {
// 控制显隐
@@ -17,6 +29,9 @@ export default function DevPanel() {
const isOpenDevPanel = useValue(false);
const hash = useValue(window.location.hash);
const stageState = useSelector((state: RootState) => state.stage);
+ const guiState = useSelector((state: RootState) => state.GUI);
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const savesState = useSelector((state: RootState) => state.saveData);
useEffect(() => {
window.onhashchange = () => {
hash.set(window.location.hash);
@@ -25,37 +40,99 @@ export default function DevPanel() {
const isShow = isShowDevPanel();
const { t, i18n } = useTranslation();
+ const setLanguage = useLanguage();
const devMainArea = (
<>
-
getPixiSscreenshot()}>Save PIXI Screenshot
-
Current Language:{i18n.language}
-
WebGAL.gameplay.pixiStage?.removeAnimation('snow-ticker')}>Remove Snow Ticker
-
Stage State
-
{JSON.stringify(stageState, null, ' ')}
+
+
+
+
+
Current Language:{i18n.language}
+
+
+
+
+
+
+
+
+ Stage State
+
+
+
+ GUI State
+
+
+
+ User Data State
+
+
+
+ Saves State
+
+
>
);
return (
<>
{isShow && isOpenDevPanel.value && (
-
-
-
isOpenDevPanel.set(false)}
- style={{ fontSize: '150%', padding: '0 0 0 15px', cursor: 'pointer' }}
- >
+
e.stopPropagation()}>
+
+
WebGAL DEV PANEL
+
-
WebGAL DEV PANEL
+
-
{devMainArea}
+
{devMainArea}
)}
{!isOpenDevPanel.value && isShow && (
-
isOpenDevPanel.set(true)} className={styles.devPanelOpener}>
+
isOpenDevPanel.set(true)} className={styles.dev_panel_opener}>
Open Dev Panel
)}
>
);
}
+
+function renderJSON(key: string, value: JSONValue) {
+ if (typeof value === 'object' && value !== null) {
+ if (Array.isArray(value)) {
+ return (
+
+
+ {key} {`[${value.length}]`}
+
+ {value.map((item, index) => renderJSON(`${index}`, item))}
+
+ );
+ } else {
+ return (
+
+
+ {key} {`[${Object.entries(value).length}]`}
+
+ {Object.entries(value).map(([subKey, subValue]) => renderJSON(subKey, subValue))}
+
+ );
+ }
+ } else {
+ return (
+
+ {key}: {String(value)}
+
+ );
+ }
+}
+
+const JsonViewer: FC
= ({ jsonString }) => {
+ let parsed: IJsonObject | null = null;
+ try {
+ parsed = JSON.parse(jsonString);
+ } catch (error) {
+ return JSON 解析失败:{(error as Error).message}
;
+ }
+
+ return {parsed && Object.entries(parsed).map(([key, value]) => renderJSON(key, value))}
;
+};
diff --git a/packages/webgal/src/UI/DevPanel/devPanel.module.scss b/packages/webgal/src/UI/DevPanel/devPanel.module.scss
index a5fa4d350..299380a33 100644
--- a/packages/webgal/src/UI/DevPanel/devPanel.module.scss
+++ b/packages/webgal/src/UI/DevPanel/devPanel.module.scss
@@ -1,22 +1,50 @@
-.devPanelMain {
- font-size: 150%;
+.dev_panel_main {
+ font-size: 4vmin;
position: absolute;
right: 0;
top: 0;
bottom: 0;
- width: 35%;
+ width: 80vmin;
background: rgba(255, 255, 255, 0.75);
z-index: 99;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ backdrop-filter: blur(1vmin);
+}
+
+.dev_panel_header {
+ display: flex;
+ align-items: center;
+}
+
+.dev_panel_title {
+ padding-left: 2vmin;
+ flex: 1 1 auto;
+ font-size: 5vmin;
+}
+
+.dev_panel_close_button {
+ font-size: 150%;
+ cursor: pointer;
+}
+
+.dev_panel_content {
+ padding: 10px 10px 10px 10px;
overflow: auto;
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
}
-.devPanelOpener {
+.dev_panel_opener {
position: absolute;
- right: 5px;
- top: 5px;
+ right: 1vmin;
+ top: 1vmin;
background: rgba(255, 255, 255, 0.75);
z-index: 100;
- padding: 3px 7px 3px 7px;
- border-radius: 4px;
+ padding: 1vmin 4vmin;
+ border-radius: 1vmin;
+ font-size: 3vmin;
cursor: pointer;
}
diff --git a/packages/webgal/src/UI/Extra/Extra.tsx b/packages/webgal/src/UI/Extra/Extra.tsx
index 8a544eb3b..c2ece896d 100644
--- a/packages/webgal/src/UI/Extra/Extra.tsx
+++ b/packages/webgal/src/UI/Extra/Extra.tsx
@@ -2,41 +2,245 @@ import styles from './extra.module.scss';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '@/store/store';
import { setVisibility } from '@/store/GUIReducer';
-import { CloseSmall } from '@icon-park/react';
+import { CloseSmall, Filter, HamburgerButton, Left, Music, Pic } from '@icon-park/react';
import { ExtraBgm } from '@/UI/Extra/ExtraBgm';
import { ExtraCg } from './ExtraCg';
import useTrans from '@/hooks/useTrans';
import useSoundEffect from '@/hooks/useSoundEffect';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { useDelayedVisibility } from '@/hooks/useDelayedVisibility';
+import { useEffect, useState } from 'react';
+import { Icon } from '@icon-park/react/lib/runtime';
+import { IAppreciationAsset } from '@/store/userDataInterface';
+
+enum ExtraType {
+ CG,
+ BGM,
+}
+
+export interface IExtraOption {
+ series: string;
+}
+
+interface IExtraBarButtonProps {
+ text?: string;
+ icon?: Icon;
+ active?: boolean;
+ onClick?: () => void;
+}
+
+interface IExtraSeriesButtonProps {
+ text: string;
+ active?: boolean;
+ onClick?: () => void;
+}
export function Extra() {
- const { playSeClick } = useSoundEffect();
+ const { playSeClick, playSeEnter } = useSoundEffect();
const showExtra = useSelector((state: RootState) => state.GUI.showExtra);
const dispatch = useDispatch();
-
+ const applyStyle = useApplyStyle('UI/Extra/extra.scss');
const t = useTrans('extra.');
+ const optionData = useSelector((state: RootState) => state.userData.optionData);
+ const [extraType, setExtraType] = useState(ExtraType.CG);
+
+ // 当前CG系列和BGM系列, 空字符串表示显示全部
+ const [currentCgSeries, setCurrentCgSeries] = useState('');
+ const [currentBgmSeries, setCurrentBgmSeries] = useState('');
+
+ // 所有CG系列和BGM系列
+ const [cgSeries, setCgSeries] = useState([]);
+ const [bgmSeries, setBgmSeries] = useState([]);
+
+ // 每次打开Extra时,遍历appreciationData,提取所有series
+ const appreciationData = useSelector((state: RootState) => state.userData.appreciationData);
+
+ // 仅在showExtra变为true时重新计算series
+ useEffect(() => {
+ if (showExtra && appreciationData) {
+ // 提取CG系列
+ const cgSet = new Set();
+ appreciationData.cg?.forEach((item) => {
+ if (item.series) {
+ cgSet.add(item.series);
+ }
+ });
+ setCgSeries(Array.from(cgSet));
+
+ // 提取BGM系列
+ const bgmSet = new Set();
+ appreciationData.bgm?.forEach((item) => {
+ if (item.series) bgmSet.add(item.series);
+ });
+ setBgmSeries(Array.from(bgmSet));
+ }
+ }, [showExtra, appreciationData]);
+
+ const extraBarButton = (props: IExtraBarButtonProps) => {
+ return (
+
+ {props.icon && (
+
+ )}
+ {props.text && (
+
{props.text}
+ )}
+
+ );
+ };
+
+ const [showFilterPanel, setShowFilterPanel] = useState(false);
+ const delayedShowFilterPanel = useDelayedVisibility(showFilterPanel);
+ const extraSeriesButton = (props: IExtraSeriesButtonProps) => {
+ return (
+
+ {props.text === 'default' ? t('defaultSeries') : props.text}
+
+ );
+ };
+
+ const useFilterTitle = () => {
+ if (extraType === ExtraType.CG) {
+ if (currentCgSeries === '') {
+ return t('title');
+ } else {
+ if (currentCgSeries === 'default') {
+ return t('defaultSeries');
+ } else {
+ return currentCgSeries;
+ }
+ }
+ } else if (extraType === ExtraType.BGM) {
+ if (currentBgmSeries === '') {
+ return t('title');
+ } else {
+ if (currentBgmSeries === 'default') {
+ return t('defaultSeries');
+ } else {
+ return currentBgmSeries;
+ }
+ }
+ }
+ return t('title');
+ };
+
+ const delayedShowExtra = useDelayedVisibility(showExtra);
+
return (
<>
- {showExtra && (
-
-
-
+
+ {extraBarButton({
+ icon: Left,
+ onClick: () => {
+ dispatch(setVisibility({ component: 'showExtra', visibility: false }));
+ playSeClick();
+ },
+ })}
+ {extraBarButton({
+ icon: HamburgerButton,
+ text: useFilterTitle(),
+ onClick: () => {
+ setShowFilterPanel((prev) => !prev);
+ },
+ })}
+ {extraBarButton({
+ icon: Pic,
+ text: t('cg'),
+ active: extraType === ExtraType.CG,
+ onClick: () => {
+ setExtraType(ExtraType.CG);
+ },
+ })}
+ {extraBarButton({
+ icon: Music,
+ text: t('bgm'),
+ active: extraType === ExtraType.BGM,
+ onClick: () => {
+ setExtraType(ExtraType.BGM);
+ },
+ })}
+ {/*
{
dispatch(setVisibility({ component: 'showExtra', visibility: false }));
playSeClick();
}}
onMouseEnter={playSeClick}
- theme="outline"
- size="4em"
- fill="#fff"
- strokeWidth={3}
+ strokeWidth={4}
/>
- {t('title')}
+ {t('title')}
*/}
-
-
-
+
+ {extraType === ExtraType.CG && }
+ {extraType === ExtraType.BGM && }
+ {delayedShowFilterPanel && (
+
setShowFilterPanel(false)}
+ >
+
e.stopPropagation()
+ }
+ >
+ {extraType === ExtraType.CG &&
+ cgSeries.map((series) =>
+ extraSeriesButton({
+ text: series,
+ active: currentCgSeries === series,
+ onClick: () => {
+ if (currentCgSeries === series) {
+ setCurrentCgSeries('');
+ } else {
+ setCurrentCgSeries(series);
+ }
+ setShowFilterPanel(false);
+ },
+ }),
+ )}
+ {extraType === ExtraType.BGM &&
+ bgmSeries.map((series) =>
+ extraSeriesButton({
+ text: series,
+ active: currentBgmSeries === series,
+ onClick: () => {
+ if (currentBgmSeries === series) {
+ setCurrentBgmSeries('');
+ } else {
+ setCurrentBgmSeries(series);
+ }
+ setShowFilterPanel(false);
+ },
+ }),
+ )}
+
+
+ )}
)}
>
diff --git a/packages/webgal/src/UI/Extra/ExtraBgm.tsx b/packages/webgal/src/UI/Extra/ExtraBgm.tsx
index 7c9bba9b2..5b00be8dc 100644
--- a/packages/webgal/src/UI/Extra/ExtraBgm.tsx
+++ b/packages/webgal/src/UI/Extra/ExtraBgm.tsx
@@ -4,22 +4,32 @@ import React from 'react';
import styles from '@/UI/Extra/extra.module.scss';
import { useValue } from '@/hooks/useValue';
import { setStage } from '@/store/stageReducer';
-import { GoEnd, GoStart, MusicList, PlayOne, SquareSmall } from '@icon-park/react';
+import { GoEnd, GoStart, MusicList, Pause, PlayOne, SquareSmall } from '@icon-park/react';
import useSoundEffect from '@/hooks/useSoundEffect';
import { setGuiAsset } from '@/store/GUIReducer';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { Icon } from '@icon-park/react/lib/runtime';
+import { IExtraOption } from './Extra';
+import useTrans from '@/hooks/useTrans';
-export function ExtraBgm() {
+interface IPlayerButtonProps {
+ icon: Icon;
+ onClick?: () => void;
+}
+
+export function ExtraBgm(props: IExtraOption) {
const { playSeClick, playSeEnter } = useSoundEffect();
// 检查当前正在播放的bgm是否在bgm列表内
const currentBgmSrc = useSelector((state: RootState) => state.GUI.titleBgm);
const extraState = useSelector((state: RootState) => state.userData.appreciationData);
+ const optionData = useSelector((state: RootState) => state.userData.optionData);
+ const t = useTrans('extra.');
+ const applyStyle = useApplyStyle('UI/Extra/extra.scss');
const initName = 'Title_BGM';
// 是否展示 bgm 列表
- const isShowBgmList = useValue(false);
+ // const isShowBgmList = useValue(false);
let foundCurrentBgmName = initName;
let foundCurrentBgmIndex = -1;
- const iconSize = 39;
- const bgmPlayerHeight = isShowBgmList.value ? '80%' : '10%';
const bgmListLen = extraState.bgm.length;
extraState.bgm.forEach((e, i) => {
if (e.url === currentBgmSrc) {
@@ -34,15 +44,18 @@ export function ExtraBgm() {
const dispatch = useDispatch();
function setBgmByIndex(index: number) {
- const e = extraState.bgm[index];
+ const e = filteredBgmList[index];
currentPlayingBgmName.set(e.name);
dispatch(setGuiAsset({ asset: 'titleBgm', value: e.url }));
}
- const showBgmList = extraState.bgm.map((e, i) => {
- let className = styles.bgmElement;
+ // 根据 series 过滤 BGM 列表
+ const filteredBgmList = props.series ? extraState.bgm.filter((bgm) => bgm.series === props.series) : extraState.bgm;
+
+ const bgmList = filteredBgmList.map((e, i) => {
+ let className = applyStyle('extra_bgm_element', styles.extra_bgm_element);
if (e.name === currentPlayingBgmName.value) {
- className = className + ' ' + styles.bgmElement_active;
+ className = className + ' ' + applyStyle('extra_bgm_element_active', styles.extra_bgm_element_active);
}
return (
- {e.name}
+
+ {e.series === 'default' ? t('defaultSeries') : e.series}
+
+
{e.name}
);
});
- // If there are no BGM tracks available, don't render the player controls
- if (bgmListLen === 0) {
- return null;
- }
+ const playerButton = (props: IPlayerButtonProps) => {
+ return (
+
+ {props.icon && (
+
+ )}
+
+ );
+ };
return (
-
-
-
{
- playSeClick();
- if (foundCurrentBgmIndex <= 0) {
- setBgmByIndex(bgmListLen - 1);
- } else {
- setBgmByIndex(foundCurrentBgmIndex - 1);
- }
- }}
- onMouseEnter={playSeEnter}
- className={styles.bgmControlButton}
- >
-
-
-
{
- playSeClick();
- const bgmControl: HTMLAudioElement = document.getElementById('currentBgm') as HTMLAudioElement;
- bgmControl?.play().then();
- }}
- onMouseEnter={playSeEnter}
- className={styles.bgmControlButton}
- >
-
-
-
{
- playSeClick();
- if (foundCurrentBgmIndex >= bgmListLen - 1) {
- setBgmByIndex(0);
- } else {
- setBgmByIndex(foundCurrentBgmIndex + 1);
- }
- }}
- onMouseEnter={playSeEnter}
- className={styles.bgmControlButton}
- >
-
-
-
{
- playSeClick();
- const bgmControl: HTMLAudioElement = document.getElementById('currentBgm') as HTMLAudioElement;
- bgmControl.pause();
- }}
- onMouseEnter={playSeEnter}
- className={styles.bgmControlButton}
- >
-
-
-
{foundCurrentBgmName}
-
{
- playSeClick();
- isShowBgmList.set(!isShowBgmList.value);
- }}
- onMouseEnter={playSeEnter}
- className={styles.bgmControlButton}
- style={{ marginLeft: 'auto' }}
- >
-
+
+
{bgmList}
+
+
{foundCurrentBgmName}
+
+ {playerButton({
+ icon: GoStart,
+ onClick: () => {
+ playSeClick();
+ if (foundCurrentBgmIndex <= 0) {
+ setBgmByIndex(bgmListLen - 1);
+ } else {
+ setBgmByIndex(foundCurrentBgmIndex - 1);
+ }
+ },
+ })}
+ {playerButton({
+ icon: PlayOne,
+ onClick: () => {
+ playSeClick();
+ const bgmControl: HTMLAudioElement = document.getElementById('currentBgm') as HTMLAudioElement;
+ bgmControl?.play().then();
+ },
+ })}
+ {playerButton({
+ icon: Pause,
+ onClick: () => {
+ playSeClick();
+ const bgmControl: HTMLAudioElement = document.getElementById('currentBgm') as HTMLAudioElement;
+ bgmControl.pause();
+ },
+ })}
+ {playerButton({
+ icon: GoEnd,
+ onClick: () => {
+ playSeClick();
+ if (foundCurrentBgmIndex >= bgmListLen - 1) {
+ setBgmByIndex(0);
+ } else {
+ setBgmByIndex(foundCurrentBgmIndex + 1);
+ }
+ },
+ })}
- {isShowBgmList.value &&
{showBgmList}
}
);
}
diff --git a/packages/webgal/src/UI/Extra/ExtraCg.tsx b/packages/webgal/src/UI/Extra/ExtraCg.tsx
index 8f610ebc7..2ebcfbe9a 100644
--- a/packages/webgal/src/UI/Extra/ExtraCg.tsx
+++ b/packages/webgal/src/UI/Extra/ExtraCg.tsx
@@ -1,72 +1,121 @@
import styles from '@/UI/Extra/extra.module.scss';
-import React from 'react';
+import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { RootState } from '@/store/store';
import { useValue } from '@/hooks/useValue';
-import './extraCG_animation_List.scss';
import { ExtraCgElement } from '@/UI/Extra/ExtraCgElement';
import useSoundEffect from '@/hooks/useSoundEffect';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { useDelayedVisibility } from '@/hooks/useDelayedVisibility';
+import { IExtraOption } from './Extra';
+import { Icon } from '@icon-park/react/lib/runtime';
+import { Close } from '@icon-park/react';
-export function ExtraCg() {
- const cgPerPage = 8;
+interface IExtraFullScreenBarButtonProps {
+ icon?: Icon;
+ onClick?: () => void;
+}
+
+export function ExtraCg(props: IExtraOption) {
const extraState = useSelector((state: RootState) => state.userData.appreciationData);
- const pageNumber = Math.ceil(extraState.cg.length / cgPerPage);
- // const pageNumber = 10;
const currentPage = useValue(1);
const { playSeEnter, playSeClick } = useSoundEffect();
+ const applyStyle = useApplyStyle('UI/Extra/extra.scss');
- // 开始生成立绘鉴赏的图片
- const showCgList = [];
- const len = extraState.cg.length;
- for (
- let i = (currentPage.value - 1) * cgPerPage;
- i < Math.min(len, (currentPage.value - 1) * cgPerPage + cgPerPage);
- i++
- ) {
- const index = i - (currentPage.value - 1) * cgPerPage;
- const deg = Random(-5, 5);
- const temp = (
-
- );
- showCgList.push(temp);
- }
+ const [showFull, setShowFull] = useState(false);
+ const delayedShowFull = useDelayedVisibility(showFull);
+ const [currentIndex, setCurrentIndex] = useState(-1);
+ const [mediaType, setMediaType] = useState<'video' | 'image' | 'unknown'>('unknown');
- // 生成cg鉴赏的导航
- const showNav = [];
- for (let i = 1; i <= pageNumber; i++) {
- let className = styles.cgNav;
- if (currentPage.value === i) {
- className = className + ' ' + styles.cgNav_active;
- }
- const temp = (
+ const extraFullScreenBarButton = (props: IExtraFullScreenBarButtonProps) => {
+ return (
{
- currentPage.set(i);
- playSeClick();
- }}
- key={'nav' + i}
+ className={`${applyStyle('extra_cg_full_screen_bar_button', styles.extra_cg_full_screen_bar_button)}`}
+ onClick={props.onClick}
onMouseEnter={playSeEnter}
- className={className}
>
- {i}
+ {props.icon && (
+
+ )}
);
- showNav.push(temp);
- }
+ };
+
+ // 根据 series 过滤 CG 列表
+ const filteredCgList = props.series ? extraState.cg.filter((cg) => cg.series === props.series) : extraState.cg;
+
+ const cgList = filteredCgList.map((cg, index) => {
+ return (
+
{
+ setCurrentIndex(idx);
+ setMediaType(mediaType);
+ setShowFull(true);
+ }}
+ />
+ );
+ });
return (
-
-
-
{showNav}
+ <>
+ {delayedShowFull && (
+
{
+ setShowFull(false);
+ playSeClick();
+ }}
+ className={`${applyStyle('extra_cg_full_screen', styles.extra_cg_full_screen)} ${
+ showFull ? '' : applyStyle('extra_cg_full_screen_hide', styles.extra_cg_full_screen_hide)
+ }`}
+ >
+ {mediaType === 'video' && (
+
+ )}
+ {mediaType === 'image' && (
+
![{filteredCgList[currentIndex].name}]({filteredCgList[currentIndex].url})
+ )}
+ {mediaType === 'unknown' && (
+
+ )}
+
+ {extraFullScreenBarButton({
+ icon: Close,
+ onClick: () => {
+ setShowFull(true);
+ playSeClick();
+ },
+ })}
+
+
+ )}
+
-
{showCgList}
-
+ >
);
}
diff --git a/packages/webgal/src/UI/Extra/ExtraCgElement.tsx b/packages/webgal/src/UI/Extra/ExtraCgElement.tsx
index 63a36287c..dd4e52825 100644
--- a/packages/webgal/src/UI/Extra/ExtraCgElement.tsx
+++ b/packages/webgal/src/UI/Extra/ExtraCgElement.tsx
@@ -2,22 +2,31 @@ import { useValue } from '@/hooks/useValue';
import styles from '@/UI/Extra/extra.module.scss';
import React, { useMemo } from 'react';
import useSoundEffect from '@/hooks/useSoundEffect';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import useTrans from '@/hooks/useTrans';
interface IProps {
name: string;
+ series?: string;
resourceUrl: string;
transformDeg: number;
index: number;
+ onClick: (index: number, type: 'video' | 'image' | 'unknown') => void;
}
export function ExtraCgElement(props: IProps) {
const showFull = useValue(false);
const { playSeEnter, playSeClick } = useSoundEffect();
+ const optionData = useSelector((state: RootState) => state.userData.optionData);
+ const t = useTrans('extra.');
+ const applyStyle = useApplyStyle('UI/Extra/extra.scss');
// Determine if the resource is a video based on file extension
const isVideo = useMemo(() => {
const extension = props.resourceUrl.split('.').pop()?.toLowerCase() || '';
- return ['mp4', 'webm', 'mkv'].includes(extension);
+ return ['mp4', 'webm', 'mkv', 'mov'].includes(extension);
}, [props.resourceUrl]);
// Determine if the resource is an image based on file extension
@@ -36,35 +45,25 @@ export function ExtraCgElement(props: IProps) {
loop
muted
playsInline
- style={{
- width: '100%',
- height: '100%',
- objectFit: 'cover',
- }}
+ className={applyStyle('extra_cg_element_video', styles.extra_cg_element_video)}
/>
);
} else if (isImage) {
return (
-
);
} else {
// Fallback for unsupported file types
return (
);
@@ -72,33 +71,28 @@ export function ExtraCgElement(props: IProps) {
};
return (
- <>
- {showFull.value && (
-
{
- showFull.set(!showFull.value);
- playSeClick();
- }}
- className={styles.showFullContainer}
- onMouseEnter={playSeEnter}
- >
-
{renderMedia(true)}
-
- )}
-
{
- showFull.set(!showFull.value);
- playSeClick();
- }}
- onMouseEnter={playSeEnter}
- style={{
- animation: `cg_softIn_${props.transformDeg} 1.5s ease-out ${100 + props.index * 100}ms forwards`,
- }}
- key={props.name}
- className={styles.cgElement}
- >
- {renderMedia(false)}
+
{
+ props.onClick(props.index, isVideo ? 'video' : isImage ? 'image' : 'unknown');
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ key={props.name}
+ className={applyStyle('extra_cg_element', styles.extra_cg_element)}
+ style={{
+ ['--ui-transition-duration' as any]: `${optionData.uiTransitionDuration}ms`,
+ ['--extra-cg-element-index' as any]: props.index,
+ }}
+ >
+ {renderMedia(false)}
+
+
{props.name}
+ {props.series && (
+
+ {props.series === 'default' ? t('defaultSeries') : props.series}
+
+ )}
- >
+
);
}
diff --git a/packages/webgal/src/UI/Extra/extra.module.scss b/packages/webgal/src/UI/Extra/extra.module.scss
index 1004f06ab..234e0e851 100644
--- a/packages/webgal/src/UI/Extra/extra.module.scss
+++ b/packages/webgal/src/UI/Extra/extra.module.scss
@@ -1,279 +1,545 @@
-.extra {
- width: 100%;
- height: 100%;
+.extra_main {
position: absolute;
+ display: flex;
+ flex-direction: column;
top: 0;
left: 0;
- z-index: 14;
- background-image: linear-gradient(135deg, #93a5cf 0%, #e4efe9 100%);;
- padding: 2em 2em 2em 2em;
- box-sizing: border-box;
-}
-
-.extra_top {
- padding: 0 0 0 0;
- display: flex;
- height: 10%;
+ width: 100%;
+ height: 100%;
+ background-color: rgb(218, 218, 218);
+ opacity: 0;
+ animation: extra_main_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
}
-.extra_top_icon {
- padding: 0.6em 0.6em 0 0.6em;
- border-radius: 1000px;
- transform: translate(0, -13px);
- cursor: pointer;
+@keyframes extra_main_show {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
}
-.extra_top_icon:hover {
- background: rgba(255, 255, 255, 0.25);
- animation: extra_icon_softin 0.25s ease-out forwards;
+.extra_main_hide {
+ pointer-events: none;
+ animation: extra_main_hide var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
}
-@keyframes extra_icon_softin {
+@keyframes extra_main_hide {
0% {
- background: rgba(255, 255, 255, 0);
+ opacity: 1;
}
100% {
- background: rgba(0, 0, 0, 0.25);
+ opacity: 0;
}
}
+.extra_bar {
+ position: absolute;
+ display: flex;
+ width: 100%;
+ background: linear-gradient(to bottom, rgb(218, 218, 218, 1), rgba(218, 218, 218, 0));
+ padding: 2vmin;
+ gap: 2vmin;
+ z-index: 2;
+ pointer-events: none;
+}
-.extra_title {
- font-family: "思源宋体", serif;
- height: 100%;
- line-height: 100%;
- font-size: 325%;
- font-weight: bold;
- color: transparent;
- background: linear-gradient(150deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 75%, #51A8DD 100%);
- -webkit-background-clip: text;
+.extra_bar_button {
+ cursor: pointer;
+ pointer-events: all;
+ display: flex;
+ flex-shrink: 0;
+ min-width: 10vmin;
+ min-height: 10vmin;
+ max-height: 10vmin;
+ align-items: center;
+ justify-content: center;
+ text-wrap: nowrap;
+ text-align: center;
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+ border-radius: 5vmin;
+ color: rgb(68, 68, 68);
+ font-size: 3.5vmin;
+ font-weight: 500;
+ padding: 1vmin 2.5vmin;
+ gap: 1vmin;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.5);
+ transition: background-color 0s;
+ }
+ &:nth-child(2) {
+ flex-shrink: 1;
+ }
+ &:nth-child(3) {
+ margin-left: auto;
+ }
}
-.mainContainer {
- box-sizing: border-box;
- padding: 0 2em 0 2em;
+.extra_bar_button_icon {
+ line-height: 0;
+ font-size: 120%;
+}
+
+.extra_bar_button_text {
display: flex;
- height: 92%;
- flex-flow: column;
+ overflow: hidden;
+ padding: 0 1vmin;
}
-.bgmContainer {
- left: 50px;
- right: 50px;
- bottom: 30px;
- position: absolute;
- overflow: auto;
- //width: 100%;
- //height: 70%;
- box-sizing: border-box;
+.extra_bar_button_active {
+ background-color: rgb(233, 233, 233);
+ box-shadow: 0 0.5vmin 0vmin rgb(199, 199, 199);
+
+ &:hover {
+ background-color: rgb(240, 240, 240);
+ }
+}
+
+.extra_container {
display: flex;
- flex-flow: column-reverse;
- align-content: center;
- background-image: linear-gradient(315deg, rgba(163, 189, 237, 0.95) 0%, rgba(105, 145, 199, 0.95) 100%);
- padding: 1em 2em 1em 2em;
- border-radius: 4px;
- transition: max-height 0.5s;
- z-index: 2;
+ flex-flow: column;
+ width: 100%;
+ height: 100%;
}
-.bgmListContainer {
- z-index: 2;
+.extra_filter_panel {
+ z-index: 3;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
bottom: 0;
- //overflow: hidden;
- width: 100%;
- box-sizing: border-box;
display: flex;
- flex: 1;
- flex-flow: row;
- justify-content: flex-start;
align-items: flex-start;
- margin: 0 0 15px 0;
- flex-wrap: wrap;
- overflow: auto;
+ flex-direction: column;
+ background-color: rgba(0, 0, 0, 0.479);
+ opacity: 0;
+ animation: extra_filter_panel_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
}
-.bgmPlayerMain {
+@keyframes extra_filter_panel_show {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+.extra_filter_panel_hide {
+ pointer-events: none;
+ animation: extra_filter_panel_hide var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+}
+
+@keyframes extra_filter_panel_hide {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+
+.extra_filter_panel_list {
+ position: relative;
display: flex;
+ flex-direction: column;
+ max-width: 80%;
+ width: auto;
+ height: 100%;
+ background-color: rgba(218, 218, 218, 1);
+ padding: 2vmin;
+ gap: 2vmin;
+ overflow: auto;
+ animation: extra_filter_panel_list_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+ outline: none;
+}
+
+@keyframes extra_filter_panel_list_show {
+ 0% {
+ transform: translateX(-50vmin);
+ }
+ 100% {
+ transform: translateX(0);
+ }
}
-.bgmControlButton {
- padding: 0.6em 1.2em 0.2em 1.2em;
- margin: 0 5px 0 5px;
- box-sizing: border-box;
- border-radius: 4px;
- border: 1px solid rgba(255, 255, 255, 0.5);
+.extra_filter_panel_list_button {
cursor: pointer;
- transition: background-color 0.33s, color 0.33s;
- flex-shrink: 0;
+ display: flex;
+ min-width: 50vmin;
+ min-height: 10vmin;
+ align-items: center;
+ text-align: center;
+ background-color: rgb(233, 233, 233);
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+ box-shadow: 0 0.5vmin 0vmin rgb(199, 199, 199);
+ border-style: solid;
+ border-width: 1vmin;
+ border-color: rgba(255, 255, 255, 0);
+ border-radius: 5vmin;
+ color: rgb(68, 68, 68);
+ font-size: 3.5vmin;
+ font-weight: 500;
+ padding: 1vmin 4vmin;
+
+ &:hover {
+ background-color: rgb(255, 255, 255);
+ transition: background-color 0s;
+ }
}
-.bgmControlButton:hover {
- box-shadow: 0 0 10px 5px rgba(255, 255, 255, 0.35);
+.extra_filter_panel_list_button_active {
+ background-color: rgb(52, 114, 185);
+ box-shadow: 0 0.5vmin 0vmin rgb(74, 105, 190);
+ border-color: rgb(83, 140, 206);
+ color: rgb(255, 255, 255);
+ text-shadow: 0 0.3vmin 0vmin rgb(8, 76, 139);
+
+ &:hover {
+ background-color: rgb(66, 123, 189);
+ }
}
-.bgmName {
- color: rgba(255, 255, 255, 0.8);
- font-family: "思源宋体", serif;
- font-size: 155%;
- margin: 5px 5px 0 15px;
- overflow: hidden;
+.extra_bgm_main {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
}
-.bgmElement {
- font-family: "思源宋体", serif;
- padding: 0.5em 1em 0.5em 1em;
- overflow: hidden;
- background-color: rgba(0, 0, 0, 0.1);
- border-radius: 5px;
- color: white;
- font-size: 125%;
- margin: 0.5em 1em 0.5em 0.5em;
- transition: background-color 1s, color 1s;
- opacity: 1;
- //animation: bgmElement_In 0.5s forwards ease-out;
+.extra_bgm_list {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+ padding: 14vmin 2vmin 2vmin 2vmin;
+ overflow: auto;
+ outline: none;
+}
+
+.extra_bgm_element {
cursor: pointer;
- width: 28%;
- flex-shrink: 0;
+ display: flex;
+ align-items: flex-start;
+ width: 100%;
+ padding: 2vmin 8vmin;
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+ color: rgb(68, 68, 68);
+ font-size: 3.5vmin;
+ font-weight: 500;
+ border-radius: 3vmin;
+ gap: 4vmin;
+ opacity: 0;
+ animation: extra_bgm_element_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) calc(var(--ui-transition-duration) / 10 * var(--extra-bgm-element-index)) forwards;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.5);
+ transition: background-color 0s;
+ }
}
-@keyframes bgmElement_In {
+@keyframes extra_bgm_element_show {
0% {
- opacity: 0.95;
- //transform: translate(-50px, 0);
+ opacity: 0;
}
100% {
opacity: 1;
}
}
-.bgmElement:hover {
- background-color: rgba(255, 255, 255, 0.65);
- color: #666;
- transition: background-color 0.5s, color 0.5s;
+.extra_bgm_element_active {
+ z-index: 1;
+ background-color: rgb(240, 240, 240);
+ box-shadow: 0 0.5vmin 1vmin rgba(0, 0, 0, 0.1);
+
+ &:hover {
+ background-color: rgb(255, 255, 255);
+ }
}
-.bgmElement_active {
- background-color: rgba(255, 255, 255, 0.85) !important;
- color: #666;
+.extra_bgm_element_series {
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-end;
+ width: 30%;
+ color: rgb(49, 89, 141);
+ text-align: end;
+ text-wrap: wrap;
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
}
-.cgMain {
- width: 100%;
- height: 88%;
+.extra_bgm_element_name {
+ display: flex;
+ width: 70%;
+ text-wrap: wrap;
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
}
-.cgContainer {
- width: 100%;
+.extra_bgm_player {
display: flex;
flex-wrap: wrap;
- justify-content: flex-start;
- align-items: flex-start;
- align-content: flex-start;
- height: 90%;
- box-sizing: border-box;
- padding: 4em 0 0 2em;
-}
-
-.cgElement {
- width: 22.5%;
- height: 37.5%;
- background-color: rgba(255, 255, 255, 0.75);
- box-shadow: 0 0 15px 5px rgba(0, 0, 0, 0.35);
- box-sizing: border-box;
- padding: 0.75em 0.75em 0.75em 0.75em;
- opacity: 0;
- margin: 1em 1em 1em 1em;
- animation-delay: 100ms;
- z-index: 1;
- position: relative;
- cursor: pointer;
+ padding: 2vmin;
+ gap: 2vmin;
+ background-color: rgb(233, 233, 233);
}
-.cgShowDiv {
- height: 8%;
- width: 100%;
+.extra_bgm_player_name {
display: flex;
- flex-flow: row;
+ align-items: center;
justify-content: center;
- align-items: flex-end;
+ color: rgb(68, 68, 68);
+ font-size: 4vmin;
+ font-weight: 500;
+ padding: 0 4vmin;
}
-.cgShowDivWarpper {
+.extra_bgm_player_button_list {
display: flex;
- flex-flow: row;
+ gap: 2vmin;
+ margin-left: auto;
+}
+
+.extra_bgm_player_button {
+ cursor: pointer;
+ display: flex;
+ min-width: 10vmin;
+ min-height: 10vmin;
+ align-items: center;
justify-content: center;
- align-items: flex-end;
- //background: rgba(255, 255, 255, 0.35);
- border-radius: 7px;
- padding: 12px 15px;
-}
-
-.cgNav {
- font-size: 170%;
- background-color: rgba(255,255,255,0.5);
- color: #666;
- padding: 0.12em 1em 0.12em 1em;
- margin: 0 0.25em 0 0.25em;
- //width: 20px;
text-align: center;
- cursor: pointer;
- transition: background-color 0.5s, color 0.5s, font-weight 0.5s;
- border-radius: 7px;
+
+ background-color: rgb(52, 114, 185);
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+ box-shadow: 0 0.5vmin 0vmin rgb(74, 105, 190);
+ border-color: rgb(83, 140, 206);
+ border-style: solid;
+ border-width: 1vmin;
+ border-radius: 5vmin;
+ color: rgb(255, 255, 255);
+ text-shadow: 0 0.3vmin 0vmin rgb(8, 76, 139);
+ font-size: 3.5vmin;
+ font-weight: 500;
+ padding: 1vmin 2vmin;
+
+ &:hover {
+ background-color: rgb(66, 123, 189);
+ transition: background-color 0s;
+ }
}
-.cgNav:first-child {
- margin-left: 0;
+.extra_bgm_player_button_icon {
+ line-height: 0;
+ font-size: 120%;
}
-.cgNav:last-child {
- margin-right: 0;
+.extra_cg_full_screen {
+ position: absolute;
+ z-index: 2;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0, 0, 0, 1);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ opacity: 0;
+ animation: extra_cg_full_screen_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+}
+
+@keyframes extra_cg_full_screen_show {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+.extra_cg_full_screen_hide {
+ pointer-events: none;
+ animation: extra_cg_full_screen_hide var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+}
+
+@keyframes extra_cg_full_screen_hide {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
}
-.cgNav_active {
- background-color: rgba(255,255,255,0.7) !important;
- color: #005CAF;
+.extra_cg_full_screen_image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
}
-.cgNav:hover {
- color: #005CAF;
- background-color: rgba(255,255,255,0.6);
+.extra_cg_full_screen_video {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
}
+.extra_cg_full_screen_media {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
-.showFullContainer {
- z-index: 13;
+.extra_cg_full_screen_video {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+.extra_cg_full_screen_bar {
+ z-index: 2;
position: absolute;
top: 0;
left: 0;
+ right: 0;
+ display: flex;
+ padding: 2vmin;
+ gap: 2vmin;
+ pointer-events: none;
+}
+
+.extra_cg_full_screen_bar_button {
+ cursor: pointer;
+ pointer-events: all;
+ display: flex;
+ flex-shrink: 0;
+ min-width: 10vmin;
+ min-height: 10vmin;
+ max-height: 10vmin;
+ align-items: center;
+ justify-content: center;
+ text-wrap: nowrap;
+ text-align: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+ border-radius: 5vmin;
+ color: rgb(233, 233, 233);
+ font-size: 3.5vmin;
+ font-weight: 500;
+ padding: 1vmin 2.5vmin;
+ gap: 1vmin;
+
+ &:hover {
+ background-color: rgba(127, 127, 127, 0.5);
+ transition: background-color 0s;
+ }
+}
+
+.extra_cg_full_screen_bar_button_icon {
+ line-height: 0;
+ font-size: 120%;
+}
+
+.extra_cg_main {
+ display: flex;
width: 100%;
height: 100%;
- background: rgba(0, 0, 0, 0.1);
+}
+
+.extra_cg_container {
+ position: relative;
display: flex;
- justify-content: center;
- align-items: center;
+ flex-wrap: wrap;
+ width: 100%;
+ height: 100%;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+ align-items: flex-start;
+ align-content: flex-start;
+ overflow: auto;
+ padding: 14vmin 2vmin 2vmin 2vmin;
}
-.showFullCgMain {
+.extra_cg_element {
+ position: relative;
cursor: pointer;
- width: 80%;
- height: 80%;
- box-sizing: border-box;
- padding: 2em 2em 2em 2em;
- background: rgba(255, 255, 255, 0.95);
- animation: fullCgIn 0.5s ease-out forwards;
+ display: flex;
+ flex-direction: column;
+ width: auto;
+ height: auto;
+ border-style: solid;
+ border-width: 0.5vmin;
+ border-color: rgba(218, 218, 218, 1);
opacity: 0;
-}
+ scale: 1;
+ z-index: 0;
+ animation: extra_cg_element_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) calc(var(--ui-transition-duration) / 10 * var(--extra-cg-element-index)) forwards;
-$initialTransform: scale(1.05, 1.05) translate(-25px, -50px) rotateX(-10deg) rotateY(10deg);
-$endTransform: scale(1, 1) translate3d(0, 0, 0);
+ &:hover {
+ border-color: rgb(65, 163, 255);
+ }
+}
-@keyframes fullCgIn {
+@keyframes extra_cg_element_show {
0% {
opacity: 0;
- transform: $initialTransform;
}
100% {
opacity: 1;
- transform: $endTransform;
}
}
+
+.extra_cg_element_video {
+ display: flex;
+ width: auto;
+ height: 25vmin;
+ background-size: cover;
+ background-position: center;
+}
+
+.extra_cg_element_image {
+ display: flex;
+ width: auto;
+ height: 25vmin;
+ background-size: cover;
+ background-position: center;
+}
+
+.extra_cg_element_media {
+ display: flex;
+ width: 30vmin;
+ height: 30vmin;
+ background-size: cover;
+ background-position: center;
+}
+
+.extra_cg_element_info {
+ position: absolute;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ padding: 1vmin 2vmin;
+ background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%);
+}
+
+.extra_cg_element_name {
+ display: flex;
+ color: rgb(255, 255, 255);
+ text-shadow: 0 0.3vmin 1vmin rgba(0, 0, 0, 0.5);
+ font-size: 3.5vmin;
+ font-weight: 500;
+}
+
+.extra_cg_element_series {
+ display: flex;
+ color: rgb(255, 255, 255, 0.9);
+ text-shadow: 0 0.3vmin 1vmin rgba(0, 0, 0, 0.5);
+ font-size: 2.5vmin;
+ font-weight: 500;
+}
diff --git a/packages/webgal/src/UI/Extra/extraCG_animation_List.scss b/packages/webgal/src/UI/Extra/extraCG_animation_List.scss
deleted file mode 100644
index 21522f6a2..000000000
--- a/packages/webgal/src/UI/Extra/extraCG_animation_List.scss
+++ /dev/null
@@ -1,123 +0,0 @@
-$initialTransform: scale(1.15, 1.15) translate(-50px, -125px) rotateX(-25deg) rotateY(25deg);
-$endTransform: scale(1, 1) translate3d(0, 0, 0);
-
-@keyframes cg_softIn_-5 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(-5deg);
- }
-}
-
-@keyframes cg_softIn_-4 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(-4deg);
- }
-}
-
-@keyframes cg_softIn_-3 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(-3deg);
- }
-}
-
-@keyframes cg_softIn_-2 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(-2deg);
- }
-}
-
-@keyframes cg_softIn_-1 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(-1deg);
- }
-}
-
-@keyframes cg_softIn_0 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(0);
- }
-}
-
-@keyframes cg_softIn_1 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(1deg);
- }
-}
-
-@keyframes cg_softIn_2 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(2deg);
- }
-}
-
-@keyframes cg_softIn_3 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(3deg);
- }
-}
-
-@keyframes cg_softIn_4 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(4deg);
- }
-}
-
-@keyframes cg_softIn_5 {
- 0% {
- opacity: 0;
- transform: $initialTransform;
- }
- 100% {
- opacity: 1;
- transform: $endTransform + rotate(5deg);
- }
-}
diff --git a/packages/webgal/src/UI/GlobalDialog/GlobalDialog.tsx b/packages/webgal/src/UI/GlobalDialog/GlobalDialog.tsx
index baaf12ac7..a4d41a1c3 100644
--- a/packages/webgal/src/UI/GlobalDialog/GlobalDialog.tsx
+++ b/packages/webgal/src/UI/GlobalDialog/GlobalDialog.tsx
@@ -1,13 +1,19 @@
import styles from './globalDialog.module.scss';
import ReactDOM from 'react-dom';
-import { useSelector } from 'react-redux';
+import { Provider, useSelector } from 'react-redux';
import { RootState, webgalStore } from '@/store/store';
import { setVisibility } from '@/store/GUIReducer';
import { useSEByWebgalStore } from '@/hooks/useSoundEffect';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { useDelayedVisibility } from '@/hooks/useDelayedVisibility';
export default function GlobalDialog() {
- const isGlobalDialogShow = useSelector((state: RootState) => state.GUI.showGlobalDialog);
- return <>{isGlobalDialogShow &&
}>;
+ const showGlobalDialog = useSelector((state: RootState) => state.GUI.showGlobalDialog);
+
+ // 全局对话框延迟退场
+ const delayedShowGlobalDialog = useDelayedVisibility(showGlobalDialog);
+
+ return <>{delayedShowGlobalDialog &&
}>;
}
interface IShowGlobalDialogProps {
@@ -18,9 +24,10 @@ interface IShowGlobalDialogProps {
rightFunc: Function;
}
-export function showGlogalDialog(props: IShowGlobalDialogProps) {
+function GlobalDialogContent(props: IShowGlobalDialogProps) {
const { playSeClick, playSeEnter } = useSEByWebgalStore();
- webgalStore.dispatch(setVisibility({ component: 'showGlobalDialog', visibility: true }));
+ const showGlobalDialog = useSelector((state: RootState) => state.GUI.showGlobalDialog);
+ const uiTransitionDuration = useSelector((state: RootState) => state.userData.optionData.uiTransitionDuration);
const handleLeft = () => {
playSeClick();
props.leftFunc();
@@ -31,26 +38,51 @@ export function showGlogalDialog(props: IShowGlobalDialogProps) {
props.rightFunc();
hideGlobalDialog();
};
- const renderElement = (
-
-
-
-
{props.title}
-
-
- {props.leftText}
-
-
- {props.rightText}
-
+ const applyStyle = useApplyStyle('UI/GlobalDialog/globalDialog.scss');
+ return (
+
{
+ // 防止触发 useWheel
+ e.stopPropagation();
+ }}
+ >
+
+
{props.title}
+
+
+ {props.leftText}
+
+
+ {props.rightText}
);
+}
+
+export function showGlobalDialog(props: IShowGlobalDialogProps) {
+ webgalStore.dispatch(setVisibility({ component: 'showGlobalDialog', visibility: true }));
setTimeout(() => {
// eslint-disable-next-line react/no-deprecated
- ReactDOM.render(renderElement, document.getElementById('globalDialogContainer'));
+ ReactDOM.render(
+
+
+ ,
+ document.getElementById('globalDialogContainer'),
+ );
}, 100);
}
diff --git a/packages/webgal/src/UI/GlobalDialog/globalDialog.module.scss b/packages/webgal/src/UI/GlobalDialog/globalDialog.module.scss
index 206d4c785..e2cb9e24d 100644
--- a/packages/webgal/src/UI/GlobalDialog/globalDialog.module.scss
+++ b/packages/webgal/src/UI/GlobalDialog/globalDialog.module.scss
@@ -1,85 +1,108 @@
-.GlobalDialog_main {
- height: 100%;
- width: 100%;
+.global_dialog_main {
position: absolute;
- z-index: 20;
- background: rgba(15, 37, 64, 0.39);
- color: white;
- opacity: 0.5;
- animation: showGlobalDialog 0.33s forwards;
display: flex;
justify-content: center;
align-items: center;
- //font-family: "WebgalUI", -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
- font-family: "思源宋体", serif;
-}
-
-.glabalDialog_container_inner {
- width: 100%;
height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- flex-flow: column;
- background: linear-gradient(to right,
- rgba(0, 92, 175, 0) 0%,
- rgba(0, 92, 175, 0.5) 33%,
- rgba(0, 92, 175, 0.85) 50%,
- rgba(0, 92, 175, 0.5) 66%,
- rgba(0, 92, 175, 0) 100%
- );
- padding: 1em 5em 1.5em 5em;
-}
-
-.glabalDialog_container {
- height: 20%;
width: 100%;
- border-top: 4px solid;
- border-bottom: 4px solid;
- border-image: linear-gradient(to right,
- rgba(255, 255, 255, 0.05) 0%,
- rgba(255, 255, 255, 0.85) 33%,
- rgba(255, 255, 255, 1) 50%,
- rgba(255, 255, 255, 0.85) 66%,
- rgba(255, 255, 255, 0.05) 100%
- ) 1;
- //padding: 1px 1px 1px 1px;
+ background: rgba(0, 0, 0, 0.75);
+ opacity: 0;
+ animation: global_dialog_main_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+ color: white;
+ pointer-events: all;
}
-.title {
- font-size: 300%;
- text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
+@keyframes global_dialog_main_show {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
}
-.button_list {
- display: flex;
- margin: auto 0 0 0;
+.global_dialog_main_hide {
+ pointer-events: none;
+ animation: global_dialog_main_hide var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
}
-.button {
- font-size: 200%;
- padding: 0.15em 1em 0.15em 1em;
- margin: 0.2em 1em 0.2em 1em;
- cursor: pointer;
- transition: background-color 0.33s, color 0.33s, font-weight 0.33s, transform 0.33s;
- text-shadow: 0 0 10px rgba(255, 255, 255, 1);
- border-radius: 5px;
- //background: rgba(0, 0, 0, 0.05);
+@keyframes global_dialog_main_hide {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
}
-.button:hover {
- font-weight: bold;
- color: #005caf;
- transform: scale(1.1, 1.1);
- text-shadow: 0 0 15px rgba(0, 0, 0, 0);
- background: rgba(255, 255, 255, 0.85);
+.global_dialog_popup {
+ display: flex;
+ flex-direction: column;
+ background-color: rgb(218, 218, 218);
+ box-shadow: 0 0.5vmin 0vmin rgba(0, 0, 0, 0.25);
+ border-style: solid;
+ border-width: 1vmin;
+ border-color: rgb(233, 233, 233);
+ border-radius: 6vmin;
+ min-width: 50vmin;
+ min-height: 25vmin;
+ max-width: 90%;
+ max-height: 90%;
+ width: auto;
+ height: auto;
+ overflow: hidden;
+ animation: global_dialog_popup_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
}
-@keyframes showGlobalDialog {
+@keyframes global_dialog_popup_show {
0% {
- opacity: 0;
+ transform: translateY(10%);
}
100% {
- opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.global_dialog_title {
+ display: flex;
+ flex-grow: 1;
+ flex-shrink: 1;
+ justify-content: center;
+ font-size: 4vmin;
+ color: rgb(68, 68, 68);
+ padding: 4vmin 8vmin;
+ text-wrap: wrap;
+ font-weight: 500;
+}
+
+.global_dialog_button_list {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10vmin, 1fr));
+ justify-content: flex-end;
+ gap: 2vmin;
+ padding: 2vmin;
+}
+
+.global_dialog_button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ text-align: center;
+ text-wrap: nowrap;
+ background-color: rgb(52, 114, 185);
+ box-shadow: 0 0.5vmin 0vmin rgb(74, 105, 190);
+ border-style: solid;
+ border-width: 1vmin;
+ border-color: rgb(83, 140, 206);
+ border-radius: 5vmin;
+ color: rgb(255, 255, 255);
+ font-size: 4vmin;
+ font-weight: 500;
+ text-shadow: 0 0.3vmin 0vmin rgb(8, 76, 139);
+ padding: 1vmin 8vmin;
+
+ &:hover {
+ background-color: rgb(66, 123, 189);
}
}
diff --git a/packages/webgal/src/UI/Logo/Logo.tsx b/packages/webgal/src/UI/Logo/Logo.tsx
deleted file mode 100644
index a1b3eb6b8..000000000
--- a/packages/webgal/src/UI/Logo/Logo.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { FC, useEffect, useRef } from 'react';
-import styles from './logo.module.scss';
-import { useSelector } from 'react-redux';
-import { RootState } from '@/store/store';
-import { useValue } from '@/hooks/useValue';
-
-/**
- * 标识
- * @constructor
- */
-const Logo: FC = () => {
- const GUIState = useSelector((state: RootState) => state.GUI);
- const logoImage = GUIState.logoImage;
- const isEnterGame = GUIState.isEnterGame;
- const currentLogoIndex = useValue(-1);
- const currentTimeOutId = useValue
(-1);
- const animationDuration = 5000;
-
- const nextImg = () => {
- clearTimeout(currentTimeOutId.value);
- if (currentLogoIndex.value < logoImage.length - 1) {
- currentLogoIndex.set(currentLogoIndex.value + 1);
- currentTimeOutId.set(setTimeout(nextImg, animationDuration));
- } else {
- currentLogoIndex.set(-1);
- }
- };
-
- useEffect(() => {
- if (isEnterGame && logoImage.length > 0) {
- /**
- * 启动 Enter Logo
- */
- currentLogoIndex.set(0);
- currentTimeOutId.set(setTimeout(nextImg, animationDuration));
- }
- }, [isEnterGame]);
-
- const currentLogoUrl = currentLogoIndex.value === -1 ? '' : logoImage[currentLogoIndex.value];
- return (
- <>
- {currentLogoIndex.value !== -1 && (
-
- )}
- {currentLogoUrl !== '' && (
-
- )}
- >
- );
-};
-
-export default Logo;
diff --git a/packages/webgal/src/UI/Logo/logo.module.scss b/packages/webgal/src/UI/Logo/logo.module.scss
deleted file mode 100644
index 4005385ee..000000000
--- a/packages/webgal/src/UI/Logo/logo.module.scss
+++ /dev/null
@@ -1,60 +0,0 @@
-.Logo_main {
- width: 100%;
- height: 100%;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- animation: change-img-anim 5s forwards;
- background-size: cover;
- z-index: 14;
-}
-@keyframes change-img-anim {
- 0%{
- opacity: 0;
- }
- 35%{
- opacity: 1;
- }
- 65%{
- opacity: 1;
- }
- 99%{
- opacity: 0;
- }
- 100%{
- opacity: 0;
- display: none;
- }
-}
-.Logo_Back {
- width: 100%;
- height: 100%;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- opacity: 1;
- bottom: 0;
- background-size: cover;
- z-index: 14;
- background: white;
-}
-
-.animationActive{
- animation: fadeout 5s forwards;
-}
-
-@keyframes fadeout {
- 0%{
- opacity: 1;
- }
- 99%{
- opacity: 0;
- }
- 100%{
- opacity: 0;
- display: none;
- }
-}
diff --git a/packages/webgal/src/UI/Menu/Menu.tsx b/packages/webgal/src/UI/Menu/Menu.tsx
index 70e329f54..5ab4a8932 100644
--- a/packages/webgal/src/UI/Menu/Menu.tsx
+++ b/packages/webgal/src/UI/Menu/Menu.tsx
@@ -7,6 +7,8 @@ import { Options } from './Options/Options';
import { useSelector } from 'react-redux';
import { RootState } from '@/store/store';
import { MenuPanelTag } from '@/store/guiInterface';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { useDelayedVisibility } from '@/hooks/useDelayedVisibility';
/**
* Menu 页面,包括存读档、选项等
@@ -14,28 +16,37 @@ import { MenuPanelTag } from '@/store/guiInterface';
*/
const Menu: FC = () => {
const GUIState = useSelector((state: RootState) => state.GUI);
+ const userData = useSelector((state: RootState) => state.userData);
+
+ // 菜单延迟退场
+ const delayedShowMenuPanel = useDelayedVisibility(GUIState.showMenuPanel);
+
+ const applyStyle = useApplyStyle('UI/Menu/menu.scss');
let currentTag;
- // let menuBgColor = 'linear-gradient(135deg, rgba(253,251,251,0.95) 0%, rgba(235,237,238,1) 100%)';
switch (GUIState.currentMenuTag) {
case MenuPanelTag.Save:
currentTag = ;
- // menuBgColor = 'linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%)';
break;
case MenuPanelTag.Load:
currentTag = ;
- // menuBgColor = 'linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%)';
break;
case MenuPanelTag.Option:
currentTag = ;
- // menuBgColor = 'linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%)';
break;
}
return (
<>
- {GUIState.showMenuPanel && (
-
-
{currentTag}
+ {delayedShowMenuPanel && (
+
)}
>
diff --git a/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx
index 85e1100d6..306ac17dc 100644
--- a/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx
+++ b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx
@@ -1,6 +1,4 @@
import styles from './menuPanel.module.scss';
-import { MenuPanelButton } from './MenuPanelButton';
-import { playBgm } from '@/Core/controller/stage/playBgm';
import { MenuPanelTag } from '@/store/guiInterface';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '@/store/store';
@@ -8,7 +6,19 @@ import { setMenuPanelTag, setVisibility } from '@/store/GUIReducer';
import { backToTitle } from '@/Core/controller/gamePlay/backToTitle';
import useTrans from '@/hooks/useTrans';
import useSoundEffect from '@/hooks/useSoundEffect';
-import { showGlogalDialog } from '@/UI/GlobalDialog/GlobalDialog';
+import { showGlobalDialog } from '@/UI/GlobalDialog/GlobalDialog';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { Icon } from '@icon-park/react/lib/runtime';
+import { FolderOpen, Home, Left, SdCard, SettingTwo } from '@icon-park/react';
+
+interface IMenuPanelButtonProps {
+ key?: string;
+ text?: string;
+ icon?: Icon;
+ active?: boolean;
+ disabled?: boolean;
+ onClick?: () => void;
+}
/**
* Menu页的底栏
@@ -18,99 +28,93 @@ export const MenuPanel = () => {
// 国际化
const t = useTrans('menu.');
- const { playSeClick, playSeDialogOpen, playSePageChange } = useSoundEffect();
+ const { playSeClick, playSeDialogOpen, playSePageChange, playSeEnter } = useSoundEffect();
const GUIState = useSelector((state: RootState) => state.GUI);
const dispatch = useDispatch();
- // 设置Menu按钮的高亮
- const SaveTagOn = GUIState.currentMenuTag === MenuPanelTag.Save ? ` ${styles.MenuPanel_button_hl}` : ``;
- const LoadTagOn = GUIState.currentMenuTag === MenuPanelTag.Load ? ` ${styles.MenuPanel_button_hl}` : ``;
- const OptionTagOn = GUIState.currentMenuTag === MenuPanelTag.Option ? ` ${styles.MenuPanel_button_hl}` : ``;
- // 设置Menu按钮的颜色
- const SaveTagColor = GUIState.currentMenuTag === MenuPanelTag.Save ? `rgba(74, 34, 93, 0.9)` : `rgba(123,144,169,1)`;
- const LoadTagColor = GUIState.currentMenuTag === MenuPanelTag.Load ? `rgba(11, 52, 110, 0.9)` : `rgba(123,144,169,1)`;
- const OptionTagColor =
- GUIState.currentMenuTag === MenuPanelTag.Option ? `rgba(81, 110, 65, 0.9)` : `rgba(123,144,169,1)`;
+ const applyStyle = useApplyStyle('UI/Menu/MenuPanel/menuPanel.scss');
- // 设置Menu图标的颜色
- const SaveIconColor = GUIState.currentMenuTag === MenuPanelTag.Save ? `rgba(74, 34, 93, 0.9)` : `rgba(123,144,169,1)`;
- const LoadIconColor =
- GUIState.currentMenuTag === MenuPanelTag.Load ? `rgba(11, 52, 110, 0.9)` : `rgba(123,144,169,1)`;
- const OptionIconColor =
- GUIState.currentMenuTag === MenuPanelTag.Option ? `rgba(81, 110, 65, 0.9)` : `rgba(123,144,169,1)`;
+ const menuPanelButton = (props: IMenuPanelButtonProps) => {
+ return (
+
+ {props.icon && (
+
+ )}
+ {props.text && (
+
{props.text}
+ )}
+
+ );
+ };
return (
-
-
{
- playSePageChange();
- if (GUIState.showTitle) return;
- dispatch(setMenuPanelTag(MenuPanelTag.Save));
- }}
- tagName={t('saving.title')}
- key="saveButton"
- />
- {
- playSePageChange();
- dispatch(setMenuPanelTag(MenuPanelTag.Load));
- }}
- tagName={t('loadSaving.title')}
- key="loadButton"
- />
- {
+
+ {menuPanelButton({
+ key: 'exitButton',
+ icon: Left,
+ text: t('exit.title'),
+ onClick: () => {
+ playSeClick();
+ dispatch(setVisibility({ component: 'showMenuPanel', visibility: false }));
+ },
+ })}
+ {menuPanelButton({
+ key: 'titleButton',
+ icon: Home,
+ text: t('title.title'),
+ onClick: () => {
playSeDialogOpen();
- showGlogalDialog({
+ showGlobalDialog({
title: t('$gaming.buttons.titleTips'),
- leftText: t('$common.yes'),
- rightText: t('$common.no'),
- leftFunc: () => {
+ leftText: t('$common.cancel'),
+ rightText: t('$common.confirm'),
+ leftFunc: () => {},
+ rightFunc: () => {
backToTitle();
dispatch(setVisibility({ component: 'showMenuPanel', visibility: false }));
},
- rightFunc: () => {},
});
- }}
- tagName={t('title.title')}
- key="titleIcon"
- />
- {
+ },
+ })}
+ {menuPanelButton({
+ key: 'saveButton',
+ icon: SdCard,
+ text: t('saving.title'),
+ active: GUIState.currentMenuTag === MenuPanelTag.Save,
+ disabled: GUIState.showTitle,
+ onClick: () => {
+ playSePageChange();
+ if (GUIState.showTitle) return;
+ dispatch(setMenuPanelTag(MenuPanelTag.Save));
+ },
+ })}
+ {menuPanelButton({
+ key: 'loadButton',
+ icon: FolderOpen,
+ text: t('loadSaving.title'),
+ active: GUIState.currentMenuTag === MenuPanelTag.Load,
+ onClick: () => {
+ playSePageChange();
+ dispatch(setMenuPanelTag(MenuPanelTag.Load));
+ },
+ })}
+ {menuPanelButton({
+ key: 'optionButton',
+ icon: SettingTwo,
+ text: t('options.title'),
+ active: GUIState.currentMenuTag === MenuPanelTag.Option,
+ onClick: () => {
playSePageChange();
dispatch(setMenuPanelTag(MenuPanelTag.Option));
- }}
- tagName={t('options.title')}
- key="optionButton"
- />
-
- {
- playSeClick();
- dispatch(setVisibility({ component: 'showMenuPanel', visibility: false }));
- }}
- tagName={t('exit.title')}
- key="exitIcon"
- />
+ },
+ })}
);
};
diff --git a/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx
index a8d47c11c..564447b0b 100644
--- a/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx
+++ b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx
@@ -2,6 +2,7 @@ import styles from './menuPanel.module.scss';
import { MenuIconMap } from './MenuIconMap';
import { IMenuPanel } from '@/UI/Menu/MenuPanel/menuPanelInterface';
import useSoundEffect from '@/hooks/useSoundEffect';
+import useApplyStyle from '@/hooks/useApplyStyle';
/**
* 菜单标签页切换按钮
@@ -10,7 +11,8 @@ import useSoundEffect from '@/hooks/useSoundEffect';
*/
export const MenuPanelButton = (props: IMenuPanel) => {
const { playSePageChange, playSeEnter } = useSoundEffect();
- let buttonClassName = styles.MenuPanel_button;
+ const applyStyle = useApplyStyle('UI/Menu/MenuPanel/menuPanel.scss');
+ let buttonClassName = applyStyle('menu_panel_button', styles.menu_panel_button);
if (props.hasOwnProperty('buttonOnClassName')) {
buttonClassName = buttonClassName + props.buttonOnClassName;
}
@@ -24,7 +26,7 @@ export const MenuPanelButton = (props: IMenuPanel) => {
onMouseEnter={playSeEnter}
style={{ ...props.style, color: props.tagColor }}
>
-
+
{props.tagName}
diff --git a/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss b/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss
index fc879cbc0..80bc917d3 100644
--- a/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss
+++ b/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss
@@ -1,51 +1,69 @@
-.MenuPanel_main {
- width: 100%;
- height: 10%;
+.menu_panel_main {
display: flex;
- justify-content: center;
- align-items: center;
- //background-color: rgba(255, 255, 255, 1);
- //box-shadow: 0 0 45px 15px rgba(0, 0, 0, 0.05);
- padding: 0 55px;
+ flex-direction: column;
+ flex-shrink: 0;
+ position: relative;
+ width: auto;
+ height: 100%;
+ padding: 2vmin 2vmin 2vmin 0;
+ gap: 2vmin;
}
-.MenuPanel_button {
- padding: 0.25em 15px 0 15px;
- margin-right: 15px;
+.menu_panel_button {
+ cursor: pointer;
display: flex;
+ min-width: 10vmin;
+ min-height: 10vmin;
+ align-items: center;
justify-content: center;
- font-size: 200%;
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
text-align: center;
- //font-weight: bold;
- border-radius: 6px;
- //width: 20%;
- min-width: 12.5%;
- cursor: pointer;
- color: rgba(123,144,169, 1);
- background: rgba(0, 0, 0, 0);
- overflow: hidden;
- //border-right: 1.5px solid rgba(0, 0, 0, 0.15);
- transition: text-shadow 0.7s, background-color 0.7s;
+ border-radius: 0 5vmin 5vmin 0;
+ color: rgb(68, 68, 68);
+ font-size: 3.5vmin;
+ font-weight: 500;
+ padding: 1vmin 4vmin 1vmin 4vmin;
+
+ &:hover {
+ transition: background-color 0s;
+ background-color: rgb(233, 233, 233);
+ }
}
-.MenuPanel_button:last-child{
- margin-right: 0;
+.menu_panel_button:first-child {
+ margin-bottom: auto;
}
-.MenuPanel_button:hover {
- background-color: rgba(245, 246, 247, 0.15);
+.menu_panel_button_active {
+ background-color: rgb(233, 233, 233);
+ box-shadow: 0 0.5vmin 0vmin rgb(199, 199, 199);
+ border-color: rgba(255, 255, 255, 0.5);
+
+ &:hover {
+ background-color: rgb(240, 240, 240);
+ }
+}
+
+.menu_panel_button_disabled {
+ cursor: not-allowed !important;
+ pointer-events: none;
+ opacity: 0.5;
}
-.MenuPanel_button:last-child {
- border-right: none;
+.menu_panel_button_icon {
+ line-height: 0;
+ font-size: 120%;
}
-.MenuPanel_button_icon {
- transform: translate(0, 0.125em);
- padding: 0 0.15em 0 0;
- margin: 0 0.15em 0 0;
+.menu_panel_button_text {
+ display: flex;
+ flex-shrink: 0;
+ padding: 0 1vmin;
+ margin-right: auto;
}
-.MenuPanel_button_hl{
- background-color: rgba(245, 246, 247, 0.35) !important;
+@container (aspect-ratio < 1.4) {
+ .menu_panel_button_text {
+ display: none;
+ }
}
diff --git a/packages/webgal/src/UI/Menu/Options/About/About.tsx b/packages/webgal/src/UI/Menu/Options/About/About.tsx
new file mode 100644
index 000000000..4d266a16b
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/About/About.tsx
@@ -0,0 +1,67 @@
+import useTrans from '@/hooks/useTrans';
+import styles from '../options.module.scss';
+import { __INFO } from '@/config/info';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+
+export default function About() {
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const t = useTrans('menu.options.pages.about.options.');
+ const applyStyle = useApplyStyle('UI/Menu/Options/options.scss');
+ return (
+
+
+
游戏名称
+
+
+ 开发人员
+
+
aaa, bbb, ccc
+
版本号
+
1.0.1
+
+
+
+
+ {t('webgal.title')}
+
+
+
+ {t('webgal.subTitle')}
+
+
+ {t('webgal.version')}
+
+
+ {__INFO.version}
+
+
+ {t('webgal.source')}
+
+
+
+ {t('webgal.contributors')}
+
+
+
+ {t('webgal.website')}
+
+
+
+
+
+ );
+}
diff --git a/packages/webgal/src/UI/Menu/Options/Display/Display.tsx b/packages/webgal/src/UI/Menu/Options/Display/Display.tsx
index 49a5a728d..28cef2c32 100644
--- a/packages/webgal/src/UI/Menu/Options/Display/Display.tsx
+++ b/packages/webgal/src/UI/Menu/Options/Display/Display.tsx
@@ -9,16 +9,18 @@ import { RootState } from '@/store/store';
import { textFont, textSize } from '@/store/userDataInterface';
import { setOptionData } from '@/store/userDataReducer';
import { useDispatch, useSelector } from 'react-redux';
-import { OptionSlider } from '../OptionSlider';
+import { NormalSlider } from '../NormalSlider';
+import useApplyStyle from '@/hooks/useApplyStyle';
export function Display() {
const userDataState = useSelector((state: RootState) => state.userData);
const dispatch = useDispatch();
const t = useTrans('menu.options.pages.display.options.');
const { isSupported: isFullscreenSupported, enter: enterFullscreen, exit: exitFullscreen } = useFullScreen();
+ const applyStyle = useApplyStyle('UI/Menu/Options/options.scss');
return (
-
+
{isFullscreenSupported && (
)}
+
+ {
+ const newValue = event.target.value;
+ dispatch(setOptionData({ key: 'uiTransitionDuration', value: Number(newValue) }));
+ setStorage();
+ }}
+ min={0}
+ max={1000}
+ />
+
@@ -69,7 +84,7 @@ export function Display() {
/>
- {
@@ -80,7 +95,7 @@ export function Display() {
/>
- {
diff --git a/packages/webgal/src/UI/Menu/Options/NormalButton.tsx b/packages/webgal/src/UI/Menu/Options/NormalButton.tsx
index 9a3e313c0..e00f64acd 100644
--- a/packages/webgal/src/UI/Menu/Options/NormalButton.tsx
+++ b/packages/webgal/src/UI/Menu/Options/NormalButton.tsx
@@ -2,17 +2,23 @@ import { ReactElement } from 'react';
import { INormalButton } from '@/UI/Menu/Options/OptionInterface';
import styles from './normalButton.module.scss';
import useSoundEffect from '@/hooks/useSoundEffect';
+import useApplyStyle from '@/hooks/useApplyStyle';
export const NormalButton = (props: INormalButton) => {
const len: number = props.textList.length;
const buttonList: Array = [];
const { playSeEnter, playSeSwitch } = useSoundEffect();
+ const applyStyle = useApplyStyle('UI/Menu/Options/normalButton.scss');
for (let i = 0; i < len; i++) {
if (i === props.currentChecked) {
const t = (
{
playSeSwitch();
props.functionList[i]();
@@ -27,7 +33,7 @@ export const NormalButton = (props: INormalButton) => {
const t = (
{
playSeSwitch();
props.functionList[i]();
diff --git a/packages/webgal/src/UI/Menu/Options/NormalOption.tsx b/packages/webgal/src/UI/Menu/Options/NormalOption.tsx
index 02cad9823..285f74427 100644
--- a/packages/webgal/src/UI/Menu/Options/NormalOption.tsx
+++ b/packages/webgal/src/UI/Menu/Options/NormalOption.tsx
@@ -1,13 +1,16 @@
+import useApplyStyle from '@/hooks/useApplyStyle';
import styles from './normalOption.module.scss';
export const NormalOption = (props: any) => {
+ const applyStyle = useApplyStyle('UI/Menu/Options/normalOption.scss');
return (
-
- {/*
{props.title}
*/}
- {/*
{props.title}
*/}
-
{props.title}
+
+
{props.title}
{props.children}
diff --git a/packages/webgal/src/UI/Menu/Options/OptionSlider.tsx b/packages/webgal/src/UI/Menu/Options/NormalSlider.tsx
similarity index 58%
rename from packages/webgal/src/UI/Menu/Options/OptionSlider.tsx
rename to packages/webgal/src/UI/Menu/Options/NormalSlider.tsx
index 17a05235f..86b1dcf56 100644
--- a/packages/webgal/src/UI/Menu/Options/OptionSlider.tsx
+++ b/packages/webgal/src/UI/Menu/Options/NormalSlider.tsx
@@ -1,14 +1,16 @@
-import './slider.css';
+import styles from './normalSlider.module.scss';
import { ISlider } from '@/UI/Menu/Options/OptionInterface';
import { useEffect, useState, useRef } from 'react';
import useSoundEffect from '@/hooks/useSoundEffect';
+import useApplyStyle from '@/hooks/useApplyStyle';
-export const OptionSlider = (props: ISlider) => {
+export const NormalSlider = (props: ISlider) => {
const { playSeEnter } = useSoundEffect();
const [currentValue, setCurrentValue] = useState(props.initValue);
const [isHovered, setIsHovered] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const inputRef = useRef
(null);
+ const applyStyle = useApplyStyle('UI/Menu/Options/normalSlider.scss');
useEffect(() => {
const handleMouseUp = () => setIsDragging(false);
@@ -30,11 +32,37 @@ export const OptionSlider = (props: ISlider) => {
const thumbWidth = sliderWidth * 0.072;
return ratio * (sliderWidth - thumbWidth) + thumbWidth / 2;
};
+ const [bubblePosition, setBubblePosition] = useState(calculateBubblePosition());
+ useEffect(() => {
+ setBubblePosition(calculateBubblePosition());
+ }, [currentValue]);
+
+ const calculatePercent = () => {
+ if (!inputRef.current) return 0;
+ const min = props.min || 0;
+ const max = props.max || 100;
+ const ratio = (currentValue - min) / (max - min);
+ return ratio * 100;
+ };
+ const [sliderPercent, setSliderPercent] = useState(calculatePercent());
+ useEffect(() => {
+ const updateSliderPercent = () => {
+ setSliderPercent(calculatePercent());
+ };
+ updateSliderPercent();
+ }, [currentValue]);
return (
-
+
+
{
/>
{(isHovered || isDragging) && (
{Number(currentValue.toFixed(1))}
diff --git a/packages/webgal/src/UI/Menu/Options/Options.tsx b/packages/webgal/src/UI/Menu/Options/Options.tsx
index f2db09af3..623f42e52 100644
--- a/packages/webgal/src/UI/Menu/Options/Options.tsx
+++ b/packages/webgal/src/UI/Menu/Options/Options.tsx
@@ -7,72 +7,99 @@ import { Display } from '@/UI/Menu/Options/Display/Display';
import { Sound } from '@/UI/Menu/Options/Sound/Sound';
import useTrans from '@/hooks/useTrans';
import useSoundEffect from '@/hooks/useSoundEffect';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { Icon } from '@icon-park/react/lib/runtime';
+import { Flashlight, Info, System as SystemIcon, VolumeNotice } from '@icon-park/react';
+import About from './About/About';
enum optionPage {
'System',
'Display',
'Sound',
+ 'About',
+}
+
+interface IOptionsBarButtonProps {
+ text?: string;
+ icon?: Icon;
+ active?: boolean;
+ onClick?: () => void;
}
export const Options: FC = () => {
const { playSeEnter, playSeSwitch } = useSoundEffect();
const currentOptionPage = useValue(optionPage.System);
+ const applyStyle = useApplyStyle('UI/Menu/Options/options.scss');
useEffect(getStorage, []);
- function getClassName(page: optionPage) {
- if (page === currentOptionPage.value) {
- return styles.Options_page_button + ' ' + styles.Options_page_button_active;
- } else return styles.Options_page_button;
- }
-
const t = useTrans('menu.options.');
- return (
-
-
-
+ const barButton = (props: IOptionsBarButtonProps) => {
+ return (
+
+ {props.icon && (
+
+ )}
+ {props.text && (
+
{props.text}
+ )}
-
-
-
{
- currentOptionPage.set(optionPage.System);
- playSeSwitch();
- }}
- className={getClassName(optionPage.System)}
- onMouseEnter={playSeEnter}
- >
- {t('pages.system.title')}
-
-
{
- currentOptionPage.set(optionPage.Display);
- playSeSwitch();
- }}
- className={getClassName(optionPage.Display)}
- onMouseEnter={playSeEnter}
- >
- {t('pages.display.title')}
-
-
{
- currentOptionPage.set(optionPage.Sound);
- playSeSwitch();
- }}
- className={getClassName(optionPage.Sound)}
- onMouseEnter={playSeEnter}
- >
- {t('pages.sound.title')}
-
-
-
- {currentOptionPage.value === optionPage.Display && }
- {currentOptionPage.value === optionPage.System && }
- {currentOptionPage.value === optionPage.Sound && }
-
+ );
+ };
+
+ return (
+
+
+ {barButton({
+ text: t('pages.system.title'),
+ icon: SystemIcon,
+ active: currentOptionPage.value === optionPage.System,
+ onClick: () => {
+ currentOptionPage.set(optionPage.System);
+ playSeSwitch();
+ },
+ })}
+ {barButton({
+ text: t('pages.display.title'),
+ icon: Flashlight,
+ active: currentOptionPage.value === optionPage.Display,
+ onClick: () => {
+ currentOptionPage.set(optionPage.Display);
+ playSeSwitch();
+ },
+ })}
+ {barButton({
+ text: t('pages.sound.title'),
+ icon: VolumeNotice,
+ active: currentOptionPage.value === optionPage.Sound,
+ onClick: () => {
+ currentOptionPage.set(optionPage.Sound);
+ playSeSwitch();
+ },
+ })}
+ {barButton({
+ text: t('pages.about.title'),
+ icon: Info,
+ active: currentOptionPage.value === optionPage.About,
+ onClick: () => {
+ currentOptionPage.set(optionPage.About);
+ playSeSwitch();
+ },
+ })}
+ {currentOptionPage.value === optionPage.Display &&
}
+ {currentOptionPage.value === optionPage.System &&
}
+ {currentOptionPage.value === optionPage.Sound &&
}
+ {currentOptionPage.value === optionPage.About &&
}
);
};
diff --git a/packages/webgal/src/UI/Menu/Options/Sound/Sound.tsx b/packages/webgal/src/UI/Menu/Options/Sound/Sound.tsx
index 99f2898a8..19582de71 100644
--- a/packages/webgal/src/UI/Menu/Options/Sound/Sound.tsx
+++ b/packages/webgal/src/UI/Menu/Options/Sound/Sound.tsx
@@ -1,6 +1,6 @@
import styles from '@/UI/Menu/Options/options.module.scss';
import { NormalOption } from '@/UI/Menu/Options/NormalOption';
-import { OptionSlider } from '@/UI/Menu/Options/OptionSlider';
+import { NormalSlider } from '@/UI/Menu/Options/NormalSlider';
import { NormalButton } from '@/UI/Menu/Options//NormalButton';
import { setOptionData } from '@/store/userDataReducer';
import { setStorage } from '@/Core/controller/storage/storageController';
@@ -8,16 +8,18 @@ import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '@/store/store';
import useTrans from '@/hooks/useTrans';
import { voiceOption } from '@/store/userDataInterface';
+import useApplyStyle from '@/hooks/useApplyStyle';
export function Sound() {
const userDataState = useSelector((state: RootState) => state.userData);
const dispatch = useDispatch();
const t = useTrans('menu.options.pages.sound.options.');
+ const applyStyle = useApplyStyle('UI/Menu/Options/options.scss');
return (
-
+
- {
@@ -28,7 +30,7 @@ export function Sound() {
/>
- {
@@ -39,7 +41,7 @@ export function Sound() {
/>
- {
@@ -50,7 +52,7 @@ export function Sound() {
/>
- {
@@ -61,7 +63,7 @@ export function Sound() {
/>
- {
@@ -71,9 +73,9 @@ export function Sound() {
}}
/>
-
+
{
dispatch(setOptionData({ key: 'voiceInterruption', value: voiceOption.yes }));
diff --git a/packages/webgal/src/UI/Menu/Options/System/About.tsx b/packages/webgal/src/UI/Menu/Options/System/About.tsx
deleted file mode 100644
index f0c2fc5bf..000000000
--- a/packages/webgal/src/UI/Menu/Options/System/About.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import useTrans from '@/hooks/useTrans';
-import { Left } from '@icon-park/react';
-import s from './about.module.scss';
-import { __INFO } from '@/config/info';
-
-export default function About(props: { onClose: () => void }) {
- const t = useTrans('menu.options.pages.system.options.about.');
- return (
-
-
-
-
-
{t('subTitle')}
-
{t('version')}
-
{__INFO.version}
-
{t('source')}
-
-
{t('contributors')}
-
-
{t('website')}
-
-
- );
-}
diff --git a/packages/webgal/src/UI/Menu/Options/System/System.tsx b/packages/webgal/src/UI/Menu/Options/System/System.tsx
index 00333ee94..0d7cadf6e 100644
--- a/packages/webgal/src/UI/Menu/Options/System/System.tsx
+++ b/packages/webgal/src/UI/Menu/Options/System/System.tsx
@@ -6,20 +6,19 @@ import { IUserData, playSpeed } from '@/store/userDataInterface';
import { getStorage, setStorage, dumpToStorageFast } from '@/Core/controller/storage/storageController';
import { useDispatch, useSelector } from 'react-redux';
import { RootState, webgalStore } from '@/store/store';
-import { showGlogalDialog } from '@/UI/GlobalDialog/GlobalDialog';
+import { showGlobalDialog } from '@/UI/GlobalDialog/GlobalDialog';
import localforage from 'localforage';
import { logger } from '@/Core/util/logger';
import useTrans from '@/hooks/useTrans';
import useLanguage from '@/hooks/useLanguage';
import languages, { language } from '@/config/language';
import { useState } from 'react';
-import About from '@/UI/Menu/Options/System/About';
import { WebGAL } from '@/Core/WebGAL';
import useSoundEffect from '@/hooks/useSoundEffect';
import savesReducer, { ISavesData, saveActions } from '@/store/savesReducer';
import { dumpFastSaveToStorage, dumpSavesToStorage } from '@/Core/controller/storage/savesController';
-import { OptionSlider } from '@/UI/Menu/Options/OptionSlider';
-import { Info } from '@icon-park/react';
+import { NormalSlider } from '@/UI/Menu/Options/NormalSlider';
+import useApplyStyle from '@/hooks/useApplyStyle';
interface IExportGameData {
userData: IUserData;
@@ -33,6 +32,7 @@ export function System() {
const setLanguage = useLanguage();
const t = useTrans('menu.options.pages.system.options.');
const { playSeDialogOpen } = useSoundEffect();
+ const applyStyle = useApplyStyle('UI/Menu/Options/options.scss');
function exportSaves() {
const gameData: IExportGameData = {
@@ -63,7 +63,7 @@ export function System() {
try {
const saveAsObj: IExportGameData = JSON.parse(saves);
playSeDialogOpen();
- showGlogalDialog({
+ showGlobalDialog({
title: t('gameSave.dialogs.import.title'),
leftText: t('$common.yes'),
rightText: t('$common.no'),
@@ -94,104 +94,90 @@ export function System() {
inputElement.click();
}
- const [showAbout, setShowAbout] = useState(false);
-
- function toggleAbout() {
- setShowAbout(!showAbout);
- }
-
return (
-
- {showAbout &&
}
- {!showAbout && (
- <>
-
- {
- const newValue = event.target.value;
- dispatch(setOptionData({ key: 'autoSpeed', value: Number(newValue) }));
- setStorage();
- }}
- />
-
-
- () => setLanguage(language[k as unknown as number] as unknown as language),
- )}
- />
-
-
- {
- playSeDialogOpen();
- showGlogalDialog({
- title: t('resetData.dialogs.clearGameSave'),
- leftText: t('$common.yes'),
- rightText: t('$common.no'),
- leftFunc: () => {
- dispatch(saveActions.resetSaves());
- dumpSavesToStorage(0, 200);
- dumpFastSaveToStorage();
- },
- rightFunc: () => {},
- });
+
+
+ {
+ const newValue = event.target.value;
+ dispatch(setOptionData({ key: 'autoSpeed', value: Number(newValue) }));
+ setStorage();
+ }}
+ />
+
+
+ () => setLanguage(language[k as unknown as number] as unknown as language),
+ )}
+ />
+
+
+ {
+ playSeDialogOpen();
+ showGlobalDialog({
+ title: t('resetData.dialogs.clearGameSave'),
+ leftText: t('$common.cancel'),
+ rightText: t('$common.confirm'),
+ leftFunc: () => {},
+ rightFunc: () => {
+ dispatch(saveActions.resetSaves());
+ dumpSavesToStorage(0, 200);
+ dumpFastSaveToStorage();
},
- () => {
- playSeDialogOpen();
- showGlogalDialog({
- title: t('resetData.dialogs.resetSettings'),
- leftText: t('$common.yes'),
- rightText: t('$common.no'),
- leftFunc: () => {
- dispatch(resetOptionSet());
- dumpToStorageFast();
- },
- rightFunc: () => {},
- });
+ });
+ },
+ () => {
+ playSeDialogOpen();
+ showGlobalDialog({
+ title: t('resetData.dialogs.resetSettings'),
+ leftText: t('$common.cancel'),
+ rightText: t('$common.confirm'),
+ leftFunc: () => {},
+ rightFunc: () => {
+ dispatch(resetOptionSet());
+ dumpToStorageFast();
},
- () => {
- playSeDialogOpen();
- showGlogalDialog({
- title: t('resetData.dialogs.clearAll'),
- leftText: t('$common.yes'),
- rightText: t('$common.no'),
- leftFunc: () => {
- dispatch(resetAllData());
- dumpToStorageFast();
- dispatch(saveActions.resetSaves());
- dumpSavesToStorage(0, 200);
- dumpFastSaveToStorage();
- },
- rightFunc: () => {},
- });
+ });
+ },
+ () => {
+ playSeDialogOpen();
+ showGlobalDialog({
+ title: t('resetData.dialogs.clearAll'),
+ leftText: t('$common.cancel'),
+ rightText: t('$common.confirm'),
+ leftFunc: () => {},
+ rightFunc: () => {
+ dispatch(resetAllData());
+ dumpToStorageFast();
+ dispatch(saveActions.resetSaves());
+ dumpSavesToStorage(0, 200);
+ dumpFastSaveToStorage();
},
- ]}
- currentChecked={3}
- />
-
-
-
-
-
-
-
- >
- )}
+ });
+ },
+ ]}
+ currentChecked={3}
+ />
+
+
+
+
);
}
diff --git a/packages/webgal/src/UI/Menu/Options/System/about.module.scss b/packages/webgal/src/UI/Menu/Options/System/about.module.scss
deleted file mode 100644
index 7cdee3bc9..000000000
--- a/packages/webgal/src/UI/Menu/Options/System/about.module.scss
+++ /dev/null
@@ -1,46 +0,0 @@
-.backButton{
- display: flex;
- justify-content: center;
- align-items: center;
- width: 50px;
- height: 50px;
- background: rgba(0,0,0,0.1);
- border-radius: 4px;
- cursor: pointer;
-}
-
-.backButton:hover{
- background: rgba(0,0,0,0.2);
-}
-
-.about{
- padding: 10px 0 0 0;
-}
-
-.icon{
- display: inline-flex;
-}
-
-.title{
- color: transparent;
- background: linear-gradient(to left, #227D51, rgba(81, 110, 65, 1));
- -webkit-background-clip: text;
- font-size: 200%;
- //border-bottom: 2px solid rgba(81, 110, 65, 0.9);
- padding: 0.15em 0.5em 0.15em 0.1em;
- font-weight: bold;
- margin-top: 20px;
-}
-
-.text{
- color: rgba(81, 110, 65, 1);
- padding: 0 0 0 10px;
- font-size: 135%;
- a{
- color: rgba(81, 110, 65, 1);
- }
-}
-
-.contributor{
- padding: 0 10px 0 0;
-}
diff --git a/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx b/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx
index 0a86b672e..503384410 100644
--- a/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx
+++ b/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx
@@ -4,10 +4,12 @@ import { RootState } from '@/store/store';
import { useFontFamily } from '@/hooks/useFontFamily';
import { useTextAnimationDuration, useTextDelay } from '@/hooks/useTextOptions';
import useTrans from '@/hooks/useTrans';
-import { getTextSize } from '@/UI/getTextSize';
import IMSSTextbox from '@/Stage/TextBox/IMSSTextbox';
import { compileSentence } from '@/Stage/TextBox/TextBox';
import { useState } from 'react';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { ITextboxProps } from '@/Stage/TextBox/types';
+import { TextBoxFilm } from '@/Stage/TextBox/TextBoxFilm';
export const TextPreview = (props: any) => {
const t = useTrans('menu.options.pages.display.options.');
@@ -17,33 +19,34 @@ export const TextPreview = (props: any) => {
const textDelay = useTextDelay(userDataState.optionData.textSpeed);
const textDuration = useTextAnimationDuration(userDataState.optionData.textSpeed);
const textboxOpacity = userDataState.optionData.textboxOpacity;
- const size = getTextSize(userDataState.optionData.textSize) + '%';
+ const size = userDataState.optionData.textSize;
const font = useFontFamily();
const userAgent = navigator.userAgent;
const isFirefox = /firefox/i.test(userAgent);
const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
const previewText = t('textPreview.text');
- const previewTextArray = compileSentence(previewText, 3);
+ const previewTextArray = compileSentence(previewText);
const showNameText = t('textPreview.title');
- const showNameArray = compileSentence(showNameText, 3);
+ const showNameArray = compileSentence(showNameText);
const isHasName = showNameText !== '';
const Textbox = IMSSTextbox;
const [previewKey, setPreviewKey] = useState(0);
+ const applyStyle = useApplyStyle('UI/Menu/Options/textPreview.scss');
+
const forcePreviewUpdate = () => {
setPreviewKey((prevKey) => prevKey + 1);
};
- const textboxProps = {
+ const textboxProps: ITextboxProps = {
textArray: previewTextArray,
isText: true,
textDelay: textDelay,
isHasName: isHasName,
showName: showNameArray,
currentConcatDialogPrev: '',
- fontSize: size,
currentDialogKey: String(previewKey),
isSafari: isSafari,
isFirefox: isFirefox,
@@ -51,21 +54,24 @@ export const TextPreview = (props: any) => {
textDuration: textDuration,
font: font,
textSizeState: size as unknown as number,
- lineLimit: 3,
isUseStroke: true,
textboxOpacity: textboxOpacity,
};
return (
-
-
+
+ {stageState.enableFilm === '' && }
+ {stageState.enableFilm !== '' && }
);
diff --git a/packages/webgal/src/UI/Menu/Options/TextPreview/textPreview.module.scss b/packages/webgal/src/UI/Menu/Options/TextPreview/textPreview.module.scss
index 420b3bd6d..ab7157ed7 100644
--- a/packages/webgal/src/UI/Menu/Options/TextPreview/textPreview.module.scss
+++ b/packages/webgal/src/UI/Menu/Options/TextPreview/textPreview.module.scss
@@ -1,11 +1,12 @@
-.textPreviewMain {
- z-index: 1;
- padding: 1em;
- min-height: 480px;
+.options_text_preview_main {
+ // padding: 1em;
+ min-height: 50vmin;
width: 100%;
+ border-radius: 3vmin;
+ overflow: hidden;
}
-.textbox {
+.options_text_preview_textbox {
width: 100%;
height: 100%;
position: relative;
diff --git a/packages/webgal/src/UI/Menu/Options/normalButton.module.scss b/packages/webgal/src/UI/Menu/Options/normalButton.module.scss
index 2f48b267e..a378bc5bc 100644
--- a/packages/webgal/src/UI/Menu/Options/normalButton.module.scss
+++ b/packages/webgal/src/UI/Menu/Options/normalButton.module.scss
@@ -1,23 +1,31 @@
-.NormalButton {
- font-size: 150%;
- box-sizing: border-box;
- padding: 0.2em 1em 0.2em 1em;
- background-color: rgba(50, 50, 50, 0.05);
- margin: 0 0.4em 0 0;
- color: rgba(160, 170, 160, 1);
- cursor: pointer;
- border-bottom: 2px solid transparent;
+.options_normal_button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 8vmin;
+ min-width: 8vmin;
+ padding: 1vmin 4vmin;
+ border-radius: 3vmin;
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+
+ color: #474747;
+ font-weight: 500;
+ font-size: 3.8vmin;
+}
+
+.options_normal_button:hover {
+ background-color: rgb(199, 199, 199);
+ transition: background-color 0s;
}
-.NormalButton:hover {
- border-bottom: 2px solid rgba(81, 110, 65, 0.9);
- color: rgba(81, 110, 65, 0.9);
- font-weight: bold;
+.options_normal_button_active {
+ transform: translateY(-0.5vmin);
+ background-color: rgb(52, 114, 185);
+ box-shadow: 0 0.5vmin 0vmin rgb(32, 96, 148);
+ color: rgb(255, 255, 255);
+ text-shadow: 0 0.3vmin 0 rgb(32, 96, 148);
}
-.NormalButtonChecked {
- background-color: rgba(81, 110, 65, 0.15);
- border-bottom: 2px solid rgba(81, 110, 65, 0.9);
- color: rgba(81, 110, 65, 0.9);
- font-weight: bold;
+.options_normal_button_active:hover {
+ background-color: rgb(52, 109, 175);
}
diff --git a/packages/webgal/src/UI/Menu/Options/normalOption.module.scss b/packages/webgal/src/UI/Menu/Options/normalOption.module.scss
index 8f368f6fc..e95094c6f 100644
--- a/packages/webgal/src/UI/Menu/Options/normalOption.module.scss
+++ b/packages/webgal/src/UI/Menu/Options/normalOption.module.scss
@@ -1,64 +1,27 @@
-.NormalOption {
- margin: 0.2em 1em 0.2em 1em;
- padding: 0.2em 0.2em 0.2em 0.2em;
+.options_normal_option {
display: flex;
flex-flow: column;
align-items: flex-start;
- animation: Elements_in ease-out 0.7s forwards;
}
-.NormalOption_title {
- //color: rgba(81, 110, 65, 0.9);
- color: transparent;
- background: linear-gradient(to left, #227D51, rgba(81, 110, 65, 1));
- -webkit-background-clip: text;
- font-size: 200%;
- //border-bottom: 2px solid rgba(81, 110, 65, 0.9);
- padding: 0.15em 0.5em 0.15em 0.1em;
- font-weight: bold;
-}
-
-.NormalOption_title_bef {
- font-weight: bold;
- font-size: 200%;
- content: attr(data-text);
- position: absolute;
- -webkit-text-stroke: 3px rgba(0, 0, 0, 1);
- z-index: -1;
- padding: 0.15em 0.5em 0.15em 0.1em;
-}
-
-.NormalOption_title_sd {
- font-weight: bold;
- color: rgba(0, 0, 0, 0);
- font-size: 200%;
- position: absolute;
- z-index: -1;
- padding: 0.15em 0.5em 0.15em 0.1em;
- text-shadow: 0.04em 0.04em rgba(81, 110, 65, 0.9),
- 0.05em 0.05em rgba(81, 110, 65, 0.9),
- 0.06em 0.06em rgba(81, 110, 65, 0.9),
- 0.07em 0.07em rgba(81, 110, 65, 0.9),
- 0.08em 0.08em rgba(81, 110, 65, 0.9),
- 0.09em 0.09em rgba(81, 110, 65, 0.9),
- 0.10em 0.10em rgba(81, 110, 65, 0.9);
- //0.11em 0.11em rgba(81, 110, 65, 0.9),
- //0.12em 0.12em rgba(81, 110, 65, 0.9);
+.options_normal_option_title {
+ display: flex;
+ flex-wrap: wrap;
+ max-width: 80%;
+ background-color: rgb(218, 218, 218);
+ color: rgb(68, 68, 68);
+ padding: 1vmin 4vmin;
+ border-radius: 4vmin 4vmin 0vmin 0vmin;
+ font-size: 3.2vmin;
+ font-weight: 500;
}
-.NormalOption_buttonList {
- padding: 0.5em 0 0.5em 0;
+.options_normal_option_content {
display: flex;
+ flex-wrap: wrap;
+ background-color: rgb(218, 218, 218);
+ box-shadow: 0 0.5vmin 0vmin rgb(199, 199, 199);
+ padding: 1vmin;
+ border-radius: 0vmin 4vmin 4vmin 4vmin;
+ gap: 1vmin;
}
-
-@keyframes Elements_in {
- 0% {
- opacity: 0;
- transform: scale(1.03, 1.03) translate(-25px, -20px);
- }
-
- 100% {
- opacity: 1;
- transform: scale(1, 1) translate(0, 0);
- }
-}
\ No newline at end of file
diff --git a/packages/webgal/src/UI/Menu/Options/normalSlider.module.scss b/packages/webgal/src/UI/Menu/Options/normalSlider.module.scss
new file mode 100644
index 000000000..3ec3cc1a1
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/normalSlider.module.scss
@@ -0,0 +1,90 @@
+.options_normal_slider_main {
+ display: flex;
+ position: relative;
+ width: 60vmin;
+ height: 8vmin;
+}
+
+.options_normal_slider_input {
+ -webkit-appearance: none;
+ appearance: none;
+ background: transparent;
+ outline: none;
+ margin: 0;
+ display: flex;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ // display: none;
+ pointer-events: fill;
+}
+
+.options_normal_slider_input::-webkit-slider-runnable-track {
+ background-color: transparent;
+ width: 1vmin;
+ height: 1vmin;
+}
+
+
+.options_normal_slider_input::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ background-color: transparent;
+ width: 1vmin;
+ height: 1vmin;
+}
+
+.options_normal_slider_input::-moz-range-thumb {
+ width: 16px;
+ height: 16px;
+ background: transparent;
+ border: none;
+}
+
+.options_normal_slider_track {
+ transform: translateY(-0.5vmin);
+ display: flex;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: rgb(233, 233, 233);
+ box-shadow: 0 0.5vmin 0vmin rgb(199, 199, 199);
+ overflow: hidden;
+ border: 1vmin solid rgb(233, 233, 233);
+ border-radius: 3vmin;
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+}
+
+.options_normal_slider_fill {
+ display: flex;
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background-color: rgb(52, 114, 185);
+ border-radius: 2vmin;
+ pointer-events: none;
+}
+
+.options_normal_slider_bubble {
+ position: absolute;
+ bottom: 135%;
+ transform: translate(-50%, 50%);
+ background: rgb(255, 255, 255);
+ color: rgb(41, 41, 41);
+ padding: 1vmin 2vmin;
+ border-radius: 2vmin;
+ font-size: 3vmin;
+ font-weight: 500;
+ white-space: nowrap;
+ pointer-events: none;
+ animation: bubble_fade_in 0.25s ease-out;
+}
+
+@keyframes bubble_fade_in {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
diff --git a/packages/webgal/src/UI/Menu/Options/options.module.scss b/packages/webgal/src/UI/Menu/Options/options.module.scss
index b4a3ef4ba..2e4611e83 100644
--- a/packages/webgal/src/UI/Menu/Options/options.module.scss
+++ b/packages/webgal/src/UI/Menu/Options/options.module.scss
@@ -1,140 +1,142 @@
-.Options_main {
- position: absolute;
- cursor: default;
- height: 90%;
+.options_main {
+ display: flex;
+ flex-direction: column;
width: 100%;
- //background: rgba(255, 255, 255, 0.65);
+ height: 100%;
+ position: relative;
}
-.Options_top {
- height: 15%;
- width: 100%;
+.options_bar {
+ position: relative;
display: flex;
+ flex-shrink: 0;
align-items: flex-start;
+ justify-content: center;
+ width: auto;
+ height: auto;
+ padding: 2vmin 0;
+ gap: 2vmin;
}
-.Options_title {
- font-family: "思源宋体", serif;
- letter-spacing: 0.1em;
- font-size: 225%;
- margin: 0.5em 0 0.5em 0;
- padding: 0.2em 2em 0.2em 1.1em;
- box-sizing: border-box;
- //background-color: rgba(255, 255, 255, 0.99);
- //border-right: .2em solid rgba(81, 110, 65, 0.9);
- //box-shadow: .1em .1em .8em .2em rgba(0, 0, 0, 0.07);
+.options_bar_button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-color: rgba(255, 255, 255, 0.0);
+ border-width: 0 0 0.8vmin 0;
+ border-style: solid;
+ min-width: 10vmin;
+ min-height: 10vmin;
+ padding: 0 2.5vmin;
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+ gap: 1vmin;
+ color: rgb(68, 68, 68);
+ font-size: 3.5vmin;
}
-.Option_title_text {
- font-size: 165%;
- font-weight: bold;
- color: transparent;
- background: linear-gradient(to left, #227D51, rgba(81, 110, 65, 1));
- -webkit-background-clip: text;
- animation: Elements_in ease-out 0.7s forwards;
+.options_bar_button:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+ transition: background-color 0s;
}
-.Option_title_text_shadow {
- position: absolute;
- color: rgba(0, 0, 0, 0);
- -webkit-text-stroke: 3px rgba(0, 0, 0, 1);
- z-index: -1;
+.options_bar_button_active {
+ border-color: rgb(68, 68, 68);
}
-.Option_title_text_ts {
- position: absolute;
- color: rgba(0, 0, 0, 0);
- text-shadow: 0.04em 0.04em rgba(81, 110, 65, 0.9),
- 0.05em 0.05em rgba(81, 110, 65, 0.9),
- 0.06em 0.06em rgba(81, 110, 65, 0.9),
- 0.07em 0.07em rgba(81, 110, 65, 0.9);
- //0.08em 0.08em rgba(81, 110, 65, 0.9),
- //0.09em 0.09em rgba(81, 110, 65, 0.9),
- //0.10em 0.10em rgba(81, 110, 65, 0.9),
- //0.11em 0.11em rgba(81, 110, 65, 0.9),
- //0.12em 0.12em rgba(81, 110, 65, 0.9);
- z-index: -1;
+.options_bar_button_icon {
+ line-height: 0;
+ font-size: 120%
}
-.Options_main_content {
+.options_bar_button_text {
display: flex;
- flex: 1;
- padding: 0 0 0 3em;
- overflow: auto;
+ flex-shrink: 0;
+ font-weight: 500;
+ margin-right: auto
}
-.Options_main_content_half {
- width: 95%;
+.options_page_container {
display: flex;
- flex-flow: row;
- align-items: flex-start;
+ flex-direction: row;
align-content: flex-start;
+ align-items: flex-end;
flex-wrap: wrap;
- padding: 0 1em 0 1em;
- position: relative;
-}
-
-.About_title_text {
- margin: 0.2em 1em 0.2em 1em;
- padding: 0.2em 0.2em 0.2em 0.2em;
- //width: 100%;
- position: absolute;
- top: 10px;
- right: 10px;
- animation: Elements_in ease-out 0.7s forwards;
- cursor: pointer;
-}
-
-.About_text {
- font-weight: bold;
- color: transparent;
- background: linear-gradient(to left, rgba(34, 125, 81, 0.65), rgba(81, 110, 65, 0.65));
- -webkit-background-clip: text;
- font-size: 135%;
- text-decoration: underline;
-
+ height: 100%;
+ overflow: auto;
+ background-color: rgb(233, 233, 233);
+ border-radius: 6vmin 0 0 0;
+ padding: 2vmin 2vmin 4vmin 2vmin;
+ gap: 4vmin;
+ animation: options_page_container_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1);
}
-@keyframes Elements_in {
+@keyframes options_page_container_show {
0% {
opacity: 0;
- transform: scale(1.03, 1.03) translate(-25px, -20px);
+ transform: translateY(2%);
}
-
100% {
- opacity: 1;
- transform: scale(1, 1) translate(0, 0);
+ opacity: 1;
+ transform: translateY(0);
}
}
-.Options_page_container {
- height: 85%;
+.options_description {
display: flex;
- padding: 1em 3.75em 1em 3.75em;
+ flex-direction: column;
+ width: 100%;
+ align-items: flex-start;
+}
+
+.options_description:last-child {
+ margin-bottom: 4vmin;
}
-.Options_button_list {
- animation: Elements_in ease-out 0.7s forwards;
+.options_description_title{
+ display: flex;
+ font-size: 3.5vmin;
+ font-weight: 500;
+ background-color: rgb(218, 218, 218);
+ color: rgb(68, 68, 68);
+ border-radius: 4vmin 4vmin 0 0;
+ padding: 1vmin 4vmin;
}
-.Options_page_button {
- font-family: "思源宋体", serif;
- font-size: 300%;
- font-weight: bold;
- color: transparent;
- background: linear-gradient(to left, #227D51, rgba(81, 110, 65, 1));
- opacity: 0.35;
- -webkit-background-clip: text;
- transition: color 0.33s, background-image 0.33s, opacity 0.33s;
- cursor: pointer;
+.options_description_content{
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ background-color: rgb(218, 218, 218);
+ box-shadow: 0 0.5vmin 0vmin rgb(199, 199, 199);
+ border-radius: 0 4vmin 4vmin 4vmin;
+ padding: 2vmin 4vmin;
}
-.Options_page_button_active {
- background-image: linear-gradient(to left, #227D51, rgba(81, 110, 65, 1));
- -webkit-background-clip: text;
- opacity: 1;
+.options_description_subtitle {
+ display: flex;
+ width: auto;
+ color: rgb(47, 47, 47);
+ font-size: 3.5vmin;
+ font-weight: 500;
+ padding: 1vmin;
+ border-radius: 2vmin;
+}
+
+.options_description_text{
+ color: rgb(47, 47, 47);
+ font-size: 3vmin;
+ font-weight: 500;
+ padding: 1vmin 3vmin;
+ a {
+ display: inline-block;
+ width: 100%;
+ white-space: normal;
+ word-break: break-word;
+ }
}
-.Options_page_button:hover {
- opacity: 1;
+@container (aspect-ratio < 1.6) {
+ .options_bar_button_text {
+ display: none;
+ }
}
diff --git a/packages/webgal/src/UI/Menu/Options/slider.css b/packages/webgal/src/UI/Menu/Options/slider.css
deleted file mode 100644
index 826e83119..000000000
--- a/packages/webgal/src/UI/Menu/Options/slider.css
+++ /dev/null
@@ -1,106 +0,0 @@
-input[type=range] {
- -webkit-appearance: none; /* 去掉底部的 track 默认样式,就是整个灰条 */
- width: 500px; /* Firefox 需要指定明确的宽度 */
- height: 50px;
- background: transparent; /* 否则在 Chrome 中是白色背景 */
- font-size: 100%;
-}
-
-/* 去掉 webkit 内核 滑块 的样式 */
-input[type=range]::-webkit-slider-thumb {
- -webkit-appearance: none;
-}
-
-input[type=range]:focus {
- outline: none; /* 去除获取焦点时蓝色的外边框,你也可以自己定制其他你想要的效果 */
-}
-
-/*以下是自定义样式*/
-
-/*滑块样式*/
-input[type=range]::-webkit-slider-thumb {
- -webkit-appearance: none;
- /*border: 1px solid #000000;*/
- height: 375%;
- width: 7.2%;
- border-radius: 5em;
- background: #ffffff;
- cursor: pointer;
- margin-top: -14px; /* 在 Chrome 中你需要给定一个明确的 margin,但是在 Firefox 和 IE 中这个是固定的 */
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); /* 添加一条炫酷的效果为你的 thumb */
-}
-
-/*轨道样式*/
-input[type=range]::-webkit-slider-runnable-track {
- width: 100%;
- height: 20%;
- cursor: pointer;
- box-shadow: 1px 1px 7px rgba(0, 0, 0, 0.3);
- background: rgba(81, 110, 65, 0.9);
- border-radius: 2em;
-}
-
-input[type=range]:focus::-webkit-slider-runnable-track {
- background: rgba(81, 110, 65, 0.9);
-}
-
-/* Firefox 同上 */
-input[type=range]::-moz-range-thumb {
- height: 36px;
- width: 7.2%;
- border-radius: 5em;
- background: #ffffff;
- cursor: pointer;
- /*margin-top: -14px; !* 在 Chrome 中你需要给定一个明确的 margin,但是在 Firefox 和 IE 中这个是固定的 *!*/
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); /* 添加一条炫酷的效果为你的 thumb */
-}
-
-input[type=range]::-moz-range-track {
- width: 100%;
- height: 20%;
- cursor: pointer;
- box-shadow: 1px 1px 7px rgba(0, 0, 0, 0.3);
- background: rgba(81, 110, 65, 0.9);
- border-radius: 2em;
-}
-
-.Option_WebGAL_slider {
- position: relative;
-}
-
-.bubble {
- position: absolute;
- bottom: calc(100% + 10px);
- transform: translateX(-50%);
- background: rgba(0, 0, 0, 0.85);
- color: white;
- padding: 6px 12px;
- border-radius: 6px;
- font-weight: bold;
- white-space: nowrap;
- pointer-events: none;
- opacity: 0;
- animation: bubbleFadeIn 0.2s ease-out forwards;
-}
-
-.bubble::after {
- content: '';
- position: absolute;
- top: 100%;
- left: 50%;
- transform: translateX(-50%);
- border-width: 6px;
- border-style: solid;
- border-color: rgba(0, 0, 0, 0.85) transparent transparent transparent;
-}
-
-@keyframes bubbleFadeIn {
- from {
- opacity: 0;
- transform: translateX(-50%) translateY(4px);
- }
- to {
- opacity: 1;
- transform: translateX(-50%) translateY(0);
- }
-}
diff --git a/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx b/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx
index 96fc6efcd..135d58649 100644
--- a/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx
+++ b/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx
@@ -1,6 +1,6 @@
import { CSSProperties, FC, useEffect } from 'react';
import { loadGame } from '@/Core/controller/storage/loadGame';
-import styles from '../SaveAndLoad.module.scss';
+import styles from '@/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss';
// import {saveGame} from '@/Core/controller/storage/saveGame';
import { setStorage } from '@/Core/controller/storage/storageController';
import { useDispatch, useSelector } from 'react-redux';
@@ -11,17 +11,36 @@ import { useTranslation } from 'react-i18next';
import useSoundEffect from '@/hooks/useSoundEffect';
import { getSavesFromStorage } from '@/Core/controller/storage/savesController';
import { easyCompile } from '@/UI/Menu/SaveAndLoad/Save/Save';
+import useApplyStyle from '@/hooks/useApplyStyle';
export const Load: FC = () => {
const { playSeClick, playSeEnter, playSePageChange } = useSoundEffect();
const userDataState = useSelector((state: RootState) => state.userData);
const saveDataState = useSelector((state: RootState) => state.saveData);
const dispatch = useDispatch();
+ const applyStyle = useApplyStyle('UI/Menu/SaveAndLoad/saveAndLoad.scss');
+
+ // 换页时将存档滚动容器拉至顶部
+ useEffect(() => {
+ const saveLoadContent = document.getElementById('saveLoadContent');
+ if (saveLoadContent) {
+ saveLoadContent.scrollTop = 0;
+ }
+ }, [userDataState.optionData.slPage]);
+
const page = [];
for (let i = 1; i <= 20; i++) {
- let classNameOfElement = styles.Save_Load_top_button + ' ' + styles.Load_top_button;
+ let classNameOfElement =
+ applyStyle('save_load_bar_button', styles.save_load_bar_button) +
+ ' ' +
+ applyStyle('load_bar_button', styles.load_bar_button);
if (i === userDataState.optionData.slPage) {
- classNameOfElement = classNameOfElement + ' ' + styles.Save_Load_top_button_on + ' ' + styles.Load_top_button_on;
+ classNameOfElement =
+ classNameOfElement +
+ ' ' +
+ applyStyle('save_load_bar_button_active', styles.save_load_bar_button_active) +
+ ' ' +
+ applyStyle('load_bar_button_active', styles.load_bar_button_active);
}
const element = (
{
key={'Load_element_page' + i}
className={classNameOfElement}
>
-
{i}
+
{i}
);
page.push(element);
@@ -59,21 +78,58 @@ export const Load: FC = () => {
const speakerView = easyCompile(speaker);
saveElementContent = (
<>
-
-
- {saveData.index}
+
+

+
+
+
+
+ {saveData.index}
+
+
+ {saveData.saveTime}
+
-
- {saveData.saveTime}
+
+
+ {speakerView}
+
+
+ {easyCompile(saveData.nowStageState.showText)}
+
-
-

-
-
-
{speakerView}
-
{easyCompile(saveData.nowStageState.showText)}
-
>
);
}
@@ -88,8 +144,10 @@ export const Load: FC = () => {
}}
onMouseEnter={playSeEnter}
key={'loadElement_' + i}
- className={styles.Save_Load_content_element}
- style={{ animationDelay: `${animationIndex * 30}ms` }}
+ className={applyStyle('save_load_content_element', styles.save_load_content_element)}
+ style={{
+ ['--save-load-content-element-index' as any]: animationIndex,
+ }}
>
{saveElementContent}
@@ -100,14 +158,9 @@ export const Load: FC = () => {
const t = useTrans('menu.');
return (
-
-
-
-
{t('loadSaving.title')}
-
-
{page}
-
-
+
diff --git a/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx b/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx
index d084a7c05..a47fbaa79 100644
--- a/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx
+++ b/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx
@@ -1,27 +1,39 @@
import { FC, useEffect } from 'react';
-import styles from '../SaveAndLoad.module.scss';
+import styles from '@/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss';
import { saveGame } from '@/Core/controller/storage/saveGame';
import { setStorage } from '@/Core/controller/storage/storageController';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '@/store/store';
import { setSlPage } from '@/store/userDataReducer';
-import { showGlogalDialog } from '@/UI/GlobalDialog/GlobalDialog';
+import { showGlobalDialog } from '@/UI/GlobalDialog/GlobalDialog';
import useTrans from '@/hooks/useTrans';
import useSoundEffect from '@/hooks/useSoundEffect';
import { getSavesFromStorage } from '@/Core/controller/storage/savesController';
import { compileSentence } from '@/Stage/TextBox/TextBox';
-import { mergeStringsAndKeepObjects } from '@/UI/Backlog/Backlog';
+import { mergeStringsAndKeepObjects } from '@/Stage/Backlog/Backlog';
+import useApplyStyle from '@/hooks/useApplyStyle';
export const Save: FC = () => {
const { playSePageChange, playSeEnter, playSeDialogOpen } = useSoundEffect();
const userDataState = useSelector((state: RootState) => state.userData);
const savesDataState = useSelector((state: RootState) => state.saveData);
const dispatch = useDispatch();
+ const applyStyle = useApplyStyle('UI/Menu/SaveAndLoad/saveAndLoad.scss');
+
+ // 换页时将存档滚动容器拉至顶部
+ useEffect(() => {
+ const saveLoadContent = document.getElementById('saveLoadContent');
+ if (saveLoadContent) {
+ saveLoadContent.scrollTop = 0;
+ }
+ }, [userDataState.optionData.slPage]);
+
const page = [];
for (let i = 1; i <= 20; i++) {
- let classNameOfElement = styles.Save_Load_top_button;
+ let classNameOfElement = applyStyle('save_load_bar_button', styles.save_load_bar_button);
if (i === userDataState.optionData.slPage) {
- classNameOfElement = classNameOfElement + ' ' + styles.Save_Load_top_button_on;
+ classNameOfElement =
+ classNameOfElement + ' ' + applyStyle('save_load_bar_button_active', styles.save_load_bar_button_active);
}
const element = (
{
key={'Save_element_page' + i}
className={classNameOfElement}
>
-
{i}
+ {i}
);
page.push(element);
@@ -61,16 +73,35 @@ export const Save: FC = () => {
const speakerView = easyCompile(speaker);
saveElementContent = (
<>
-
-
{saveData.index}
-
{saveData.saveTime}
-
-
-

+
+
-
-
{speakerView}
-
{easyCompile(saveData.nowStageState.showText)}
+
+
+
+ {saveData.index}
+
+
+ {saveData.saveTime}
+
+
+
+
+ {speakerView}
+
+
+ {easyCompile(saveData.nowStageState.showText)}
+
+
>
);
@@ -83,15 +114,15 @@ export const Save: FC = () => {
onClick={() => {
if (savesDataState.saveData[i]) {
playSeDialogOpen();
- showGlogalDialog({
+ showGlobalDialog({
title: t('saving.isOverwrite'),
- leftText: tCommon('yes'),
- rightText: tCommon('no'),
- leftFunc: () => {
+ leftText: tCommon('cancel'),
+ rightText: tCommon('confirm'),
+ leftFunc: () => {},
+ rightFunc: () => {
saveGame(i);
setStorage();
},
- rightFunc: () => {},
});
} else {
playSePageChange();
@@ -100,8 +131,10 @@ export const Save: FC = () => {
}}
onMouseEnter={playSeEnter}
key={'saveElement_' + i}
- className={styles.Save_Load_content_element}
- style={{ animationDelay: `${animationIndex * 30}ms` }}
+ className={applyStyle('save_load_content_element', styles.save_load_content_element)}
+ style={{
+ ['--save-load-content-element-index' as any]: animationIndex,
+ }}
>
{saveElementContent}
@@ -112,14 +145,11 @@ export const Save: FC = () => {
const t = useTrans('menu.');
return (
-
-
-
-
{page}
+
+
+ {page}
-
@@ -127,7 +157,7 @@ export const Save: FC = () => {
};
export function easyCompile(sentence: string) {
- const compiledNodes = compileSentence(sentence, 3, true);
+ const compiledNodes = compileSentence(sentence);
const rnodes = compiledNodes.map((line) => {
return line.map((c) => {
return c.reactNode;
diff --git a/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss b/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss
index a0f623ed2..2225cd662 100644
--- a/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss
+++ b/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss
@@ -1,267 +1,227 @@
-.Save_Load_main {
- height: 90%;
+.save_load_main {
+ display: flex;
+ flex-direction: row-reverse;
+ height: 100%;
width: 100%;
- position: absolute;
- cursor: default;
}
-.Save_Load_top {
- height: 10%;
- width: 100%;
+.save_load_bar {
+ position: relative;
display: flex;
- //background-color: rgba(255, 255, 255, 1);
- //box-shadow: 0 0 1.5em 0.1em rgba(0, 0, 0, 0.05);
- animation: Elements_in ease-out 1s forwards;
- //border-bottom: 1px solid rgba(0, 0, 0, 0.1);
- justify-content: center;
-}
-
-.Save_Load_title {
- font-family: "思源宋体", serif;
- letter-spacing: 0.1em;
+ flex-direction: column;
+ flex-shrink: 0;
+ align-items: flex-start;
width: auto;
- font-size: 500%;
- min-width: 350px;
- //margin: 0 0 0 0.8em;
- //padding: 0 0.8em 0 0.8em;
- box-sizing: border-box;
- //border-bottom: 4px solid #77428D;
- display: flex;
- justify-content: center;
- align-items: center;
- position: absolute;
- left: 20px;
- top:0;
- z-index: -1;
- opacity: 0.2;
- transform: translateY(-10px);
-}
-
-.Save_title_text {
- font-weight: bold;
- color: transparent;
- background: linear-gradient(135deg, #77428D 0%, #B28FCE 100%);
- text-shadow: 2px 2px 15px rgba(255, 255, 255, 0.5);
- -webkit-background-clip: text;
+ height: auto;
+ padding: 2vmin 2vmin;
+ gap: 2vmin;
+ overflow: auto;
+ scrollbar-width: none;
+ outline: none;
}
-
-.Load_title_text {
- font-weight: bold;
- color: transparent;
- background: linear-gradient(135deg, #005CAF 0%, #2EA9DF 100%);
- text-shadow: 2px 2px 15px rgba(255, 255, 255, 0.5);
- -webkit-background-clip: text;
-}
-
-.Save_Load_top_buttonList {
- height: 100%;
- display: flex;
- //padding: 0 0 0 2em;
-}
-
-.Save_Load_top_button {
+.save_load_bar_button {
cursor: pointer;
- font-size: 200%;
- width: 2.05em;
- text-align: center;
- color: rgba(0, 0, 0, 0.5);
- box-sizing: border-box;
display: flex;
+ min-width: 10vmin;
+ min-height: 10vmin;
align-items: center;
- border-bottom: 4px solid rgba(0, 0, 0, 0);
- transition: background-color 0.7s, border-bottom-width 0.7s;
-}
-
-.Save_Load_top_button_text {
+ justify-content: center;
text-align: center;
- width: 100%;
- padding: 0 0 3px 0;
- border-left: 2px solid rgba(0, 0, 0, 0.1);
-}
-
-.Save_Load_top_button:first-child > div {
- border-left: 2px solid rgba(0, 0, 0, 0);
+ background-color: rgb(233, 233, 233);
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+ box-shadow: 0 0.5vmin 0vmin rgb(199, 199, 199);
+ border-style: solid;
+ border-width: 1vmin;
+ border-color: rgba(255, 255, 255, 0);
+ border-radius: 5vmin;
+ color: rgb(68, 68, 68);
+ font-size: 4vmin;
+ font-weight: 500;
+
+ &:hover {
+ background-color: rgb(255, 255, 255);
+ transition: background-color 0s;
+ }
}
-.Save_Load_top_button_on {
- font-weight: bold;
- border-bottom: 4px solid #77428D;
- color: #77428D;
- background-color: rgba(119, 66, 141, 0.05);
-}
+.save_load_bar_button_active {
+ background-color: rgb(119, 66, 141);
+ box-shadow: 0 0.5vmin 0vmin rgb(133, 79, 158);
+ border-color: rgb(158, 98, 177);
+ text-shadow: 0 0.3vmin 0vmin rgb(98, 49, 121);
+ color: rgb(255, 255, 255);
-.Save_Load_top_button:hover {
- color: #77428D;
- font-weight: bold;
- border-bottom: 4px solid #77428D;
+ &:hover {
+ background-color: rgb(131, 71, 156);
+ }
}
-.Load_top_button_on {
- font-weight: bold;
- border-bottom: 5px solid #005CAF;
- color: #005CAF;
- background-color: rgba(0, 92, 175, 0.1);
-}
+.load_bar_button_active {
+ background-color: rgb(52, 114, 185);
+ box-shadow: 0 0.5vmin 0vmin rgb(74, 105, 190);
+ border-color: rgb(83, 140, 206);
+ text-shadow: 0 0.3vmin 0vmin rgb(8, 76, 139);
-.Load_top_button:hover {
- color: #005CAF;
- font-weight: bold;
- border-bottom: 5px solid #005CAF;
+ &:hover {
+ background-color: rgb(66, 123, 189);
+ }
}
-.Save_Load_content {
- height: 90%;
- width: 100%;
+.save_load_content {
display: flex;
flex-wrap: wrap;
- justify-content: space-evenly;
- align-items: center;
+ flex-direction: row;
+ height: 100%;
+ width: 100%;
+ padding: 2vmin;
+ gap: 2vmin;
+ align-content: flex-start;
+ justify-content: center;
+ overflow: auto;
+ outline: none;
}
-.Save_Load_content_element {
- //background: linear-gradient(-45deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.5) 100%);
- background: linear-gradient(-45deg, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.075));
+.save_load_content_element {
+ cursor: pointer;
+ display: flex;
+ flex-shrink: 0;
+ width: 100%;
+ height: 18vmin;
+ background-color: rgb(233, 233, 233);
+ box-shadow: 0 0.5vmin rgb(199, 199, 199);
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+ border-style: solid;
+ border-width: 1vmin;
+ border-color: rgba(255, 255, 255, 0.0);
+ border-radius: 2vmin;
overflow: hidden;
- //border: 1px solid rgba(255, 255, 255, 1);
- width: 17.5%;
- height: 45%;
- //box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
- animation: Elements_in 1s ease-out forwards, Elements_in_transform 1s ease-out;
+ gap: 1vmin;
opacity: 0;
- border-radius: 4px;
- transition: transform 0.25s, box-shadow 0.25s;
- cursor: pointer;
+ animation: save_load_content_element_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards calc(var(--ui-transition-duration) / 10 * var(--save-load-content-element-index));
}
-.Save_Load_content_element:hover {
- //box-shadow: 0 0 25px 8px rgba(0, 0, 0, 0.1);
- transform: scale(1.05, 1.05) translate(-0.2em, -0.2em);
+@keyframes save_load_content_element_show {
+ 0% {
+ opacity: 0;
+ scale: 0.95;
+ }
+ 100% {
+ opacity: 1;
+ scale: 1;
+ }
}
+.save_load_content_element:hover {
+ background-color: rgb(255, 255, 255, 1);
+ transition: background-color 0s;
+}
-.Save_Load_content_element_top {
- font-family: "思源宋体", serif;
- width: 100%;
- height: 12%;
+.save_load_content_element_preview {
display: flex;
+ flex-shrink: 0;
+ width: auto;
+ height: 100%;
+ border-radius: 1vmin;
+ overflow: hidden;
}
-.Save_Load_content_element_top_index {
- color: rgba(255, 255, 255, 1);
- text-align: center;
- font-size: 155%;
+.save_load_content_element_preview_image {
+ display: flex;
+ flex-shrink: 0;
+ background-size: cover;
height: 100%;
- width: 20%;
- background-color: #B28FCE;
+ width: 100%;
+ background-position: center;
}
-.Load_content_elememt_top_index {
- background-color: #51A8DD;
+.save_load_content_element_info {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
}
-.Save_Load_content_element_top_date {
- padding: 0.425em 0 0 0.5em;
+.save_load_content_element_info_bar {
+ display: flex;
+ width: 100%;
+ flex-shrink: 0;
background-color: #77428D;
- color: rgba(255, 255, 255, 1);
- font-size: 115%;
- height: 100%;
- width: 80%;
- font-family: WebgalUI, serif;
- letter-spacing: 0.1em;
+ border-radius: 1vmin;
+ overflow: hidden;
}
-.Load_content_element_top_date {
+.load_content_element_info_bar {
background-color: #005CAF;
}
-.Save_Load_content_text {
- font-family: "WebgalUI", sans-serif;
- letter-spacing: 0.05em;
- color: #373C38;
- background: linear-gradient(-45deg, rgba(255, 255, 255, 0.75) 0%, rgba(255, 255, 255, 1) 100%);
- //background: rgba(255,255,255,1);
- font-size: 120%;
- height: 40%;
- width: 100%;
- //box-sizing: border-box;
+.save_load_content_element_index {
display: flex;
- flex-flow: column;
- justify-content: flex-start;
- align-items: flex-start;
+ align-items: center;
+ justify-content: center;
+ height: auto;
+ padding: 0.5vmin 2vmin;
+ color: rgba(255, 255, 255, 1);
+ font-size: 3vmin;
+ background-color: #9e62b1;
}
-.Save_Load_content_text_padding {
- padding: 0.2em 0.75em 0.2em 0.75em;
+.load_content_element_index {
+ background-color: #3396d3;
}
-.Save_Load_content_speaker {
- box-sizing: border-box;
- //margin: 0.35em 0 0 0;
- //background: rgba(0, 0, 0, 0.04);
- font-weight: bold;
- color: #77428D;
- padding: 0.35em 0.8em 0.25em 0.8em;
+.save_load_content_element_date {
+ display: flex;
+ align-items: center;
+ height: auto;
width: 100%;
- //border-radius: 4px;
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-}
-
-.Load_content_speaker {
- color: #005caf;
+ padding: 0.5vmin 2vmin;
+ color: rgba(255, 255, 255, 1);
+ font-size: 3vmin;
}
-
-.Load_content_text {
- background-color: rgba(0, 92, 175, 0.75);
+.load_content_element_top_date {
}
-.Save_Load_content_miniRen {
+.save_load_content_dialog {
+ display: flex;
+ flex-direction: column;
width: 100%;
- height: 48%;
- position: relative;
- background-size: cover;
+ height: 100%;
}
-.Save_Load_content_miniRen_bg {
- background-size: cover;
- height: 100%;
+.save_load_content_speaker {
+ display: flex;
width: 100%;
- background-position: center;
+ color: #77428D;
+ font-size: 3.5vmin;
+ font-weight: 700;
+ padding: 0 2vmin;
}
-.Save_Load_content_miniRen_figure {
- height: 100%;
- max-height: 100%;
- max-width: 100%;
- position: absolute;
- bottom: 0;
+.save_load_content_text {
+ color: #474747;
+ font-size: 3.5vmin;
+ font-weight: 400;
+ padding: 0 2vmin;
}
-.Save_Load_content_miniRen_figLeft {
- bottom: 0;
- left: 0;
-}
-.Save_Load_content_miniRen_figRight {
- bottom: 0;
- right: 0;
+.load_content_speaker {
+ color: #005caf;
}
-@keyframes Elements_in {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
+.load_content_text {
+ background-color: rgba(0, 92, 175, 0.75);
}
-@keyframes Elements_in_transform {
- 0% {
- transform: scale(1.05, 1.05) translate(-25px, -20px) rotateY(15deg) rotateX(-15deg);
+@container (aspect-ratio < 1.2) {
+ .save_load_content_element {
+ flex-direction: column;
+ height: 66vmin;
}
- 100% {
- transform: scale(1, 1) translate(0, 0);
+ .save_load_content_element_preview {
+ width: 100%;
+ height: auto;
}
}
diff --git a/packages/webgal/src/UI/Menu/menu.module.scss b/packages/webgal/src/UI/Menu/menu.module.scss
index 143af6a87..e440e1f96 100644
--- a/packages/webgal/src/UI/Menu/menu.module.scss
+++ b/packages/webgal/src/UI/Menu/menu.module.scss
@@ -1,24 +1,41 @@
-.Menu_main {
+.menu_main {
+ position: absolute;
+ display: flex;
+ justify-content: center;
width: 100%;
height: 100%;
- position: absolute;
- z-index: 16;
- //backdrop-filter: blur(1px);
- animation: Menu_ShowSoftly 0.5s forwards;
- background-image: linear-gradient(to top, #accbee 0%, #e7f0fd 100%);
+ background-color: rgb(218, 218, 218);
+ animation: menu_main_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+ container-type: size;
}
-.Menu_TagContent {
- width: 100%;
- height: 90%;
+@keyframes menu_main_show {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
}
+.menu_main_hide {
+ pointer-events: none;
+ animation: menu_main_hide var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+}
-@keyframes Menu_ShowSoftly {
+@keyframes menu_main_hide {
0% {
- opacity: 0;
+ opacity: 1;
}
100% {
- opacity: 1;
+ opacity: 0;
}
}
+
+.menu_tag_content {
+ display: flex;
+ flex-grow: 1;
+ flex-shrink: 1;
+ position: relative;
+ overflow: hidden;
+}
diff --git a/packages/webgal/src/UI/PanicOverlay/PanicOverlay.tsx b/packages/webgal/src/UI/PanicOverlay/PanicOverlay.tsx
index 168e5e445..87b9024ee 100644
--- a/packages/webgal/src/UI/PanicOverlay/PanicOverlay.tsx
+++ b/packages/webgal/src/UI/PanicOverlay/PanicOverlay.tsx
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
import { useSelector } from 'react-redux';
import { RootState } from '@/store/store';
import PanicYoozle from './PanicYoozle/PanicYoozle';
+import useApplyStyle from '@/hooks/useApplyStyle';
import styles from './panicOverlay.module.scss';
export default function PanicOverlay() {
@@ -11,12 +12,17 @@ export default function PanicOverlay() {
const globalVars = useSelector((state: RootState) => state.userData.globalGameVar);
const panic = globalVars['Show_panic'];
const hidePanic = panic === false;
+
+ const applyStyle = useApplyStyle('UI/PanicOverlay/panicOverlay.scss');
+
useEffect(() => {
const isShowOverlay = GUIStore.showPanicOverlay && !hidePanic;
setShowOverlay(isShowOverlay);
}, [GUIStore.showPanicOverlay, hidePanic]);
return ReactDOM.createPortal(
-
,
- document.querySelector('#html-body__panic-overlay')!,
+
,
+ document.querySelector('div#panicOverlay')!,
);
}
diff --git a/packages/webgal/src/UI/PanicOverlay/PanicYoozle/PanicYoozle.tsx b/packages/webgal/src/UI/PanicOverlay/PanicYoozle/PanicYoozle.tsx
index 988555e30..daf2ce609 100644
--- a/packages/webgal/src/UI/PanicOverlay/PanicYoozle/PanicYoozle.tsx
+++ b/packages/webgal/src/UI/PanicOverlay/PanicYoozle/PanicYoozle.tsx
@@ -1,7 +1,9 @@
+import useApplyStyle from '@/hooks/useApplyStyle';
import { useEffect } from 'react';
import styles from './panicYoozle.module.scss';
export default function PanicYoozle() {
+ const applyStyle = useApplyStyle('UI/PanicOverlay/PanicYoozle/panicYoozle.scss');
useEffect(() => {
const panicTitle = 'Yoozle Search';
const originalTitle = document.title;
@@ -11,24 +13,41 @@ export default function PanicYoozle() {
};
}, []);
return (
-
-
+
+
-
- W
+ W
+ e
+ b
+ g
+
+ a
- e
- b
- g
- a
- l
+ l
-
-
-
diff --git a/packages/webgal/src/UI/PanicOverlay/PanicYoozle/panicYoozle.module.scss b/packages/webgal/src/UI/PanicOverlay/PanicYoozle/panicYoozle.module.scss
index 696d0d628..42b06f1a4 100644
--- a/packages/webgal/src/UI/PanicOverlay/PanicYoozle/panicYoozle.module.scss
+++ b/packages/webgal/src/UI/PanicOverlay/PanicYoozle/panicYoozle.module.scss
@@ -1,31 +1,31 @@
-.yoozle_blue {
+.panic_yoozle_blue {
color: #4285f4;
}
-.yoozle_red {
+.panic_yoozle_red {
color: #db4437;
}
-.yoozle_yellow {
+.panic_yoozle_yellow {
color: #f4b400;
}
-.yoozle_green {
+.panic_yoozle_green {
color: #0f9d58;
}
-.yoozle_e_rotate {
+.panic_yoozle_e_rotate {
display: inline-block;
transform: rotate(-12deg);
}
-.yoozle_container {
+.panic_yoozle_container {
display: flex;
flex-direction: column;
height: 100%;
}
-.yoozle_title {
+.panic_yoozle_title {
display: flex;
flex-direction: column;
align-items: center;
@@ -34,25 +34,25 @@
font-size: 90px;
}
-.yoozle_search {
+.panic_yoozle_search {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 10px;
}
-.yoozle_search_bar {
+.panic_yoozle_search_bar {
width: 40%;
line-height: 32px;
font-family: arial, sans-serif;
font-size: 18px;
}
-.yoozle_search_buttons {
+.panic_yoozle_search_buttons {
padding-top: 13px;
}
-.yoozle_button {
+.panic_yoozle_button {
background-color: #f8f9fa;
border: 1px solid #f8f9fa;
border-radius: 4px;
@@ -69,13 +69,13 @@
user-select: none;
}
-.yoozle_button:hover {
+.panic_yoozle_button:hover {
box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
border: 1px solid #dadce0;
color: #202124;
}
-.yoozle_button:focus {
+.panic_yoozle_button:focus {
border: 1px solid #4285f4;
outline: none;
}
diff --git a/packages/webgal/src/UI/PanicOverlay/panicOverlay.module.scss b/packages/webgal/src/UI/PanicOverlay/panicOverlay.module.scss
index 7619d39d1..9348031d6 100644
--- a/packages/webgal/src/UI/PanicOverlay/panicOverlay.module.scss
+++ b/packages/webgal/src/UI/PanicOverlay/panicOverlay.module.scss
@@ -1,7 +1,5 @@
.panic_overlay_main {
- margin: 0;
- position: fixed;
- //display: none;
+ display: flex;
width: 100%;
height: 100%;
background-color: white;
diff --git a/packages/webgal/src/UI/Title/Title.tsx b/packages/webgal/src/UI/Title/Title.tsx
index baae72dd4..57642ad71 100644
--- a/packages/webgal/src/UI/Title/Title.tsx
+++ b/packages/webgal/src/UI/Title/Title.tsx
@@ -10,9 +10,20 @@ import { keyboard } from '@/hooks/useHotkey';
import useConfigData from '@/hooks/useConfigData';
import { playBgm } from '@/Core/controller/stage/playBgm';
import { continueGame, startGame } from '@/Core/controller/gamePlay/startContinueGame';
-import { showGlogalDialog } from '../GlobalDialog/GlobalDialog';
+import { showGlobalDialog } from '../GlobalDialog/GlobalDialog';
import styles from './title.module.scss';
+import { Icon } from '@icon-park/react/lib/runtime';
+import { FolderOpen, GoOn, PlayOne, Power, SettingTwo, Star } from '@icon-park/react';
+import { useDelayedVisibility } from '@/hooks/useDelayedVisibility';
+
+interface ITitleButtonProps {
+ text?: string;
+ icon?: Icon;
+ disabled?: boolean;
+ onClick?: () => void;
+}
+
/** 标题页 */
export default function Title() {
const userDataState = useSelector((state: RootState) => state.userData);
@@ -31,11 +42,31 @@ export default function Title() {
const appreciationItems = useSelector((state: RootState) => state.userData.appreciationData);
const hasAppreciationItems = appreciationItems.bgm.length > 0 || appreciationItems.cg.length > 0;
+ const titleButton = (props: ITitleButtonProps) => {
+ return (
+
+ {props.icon && (
+
+ )}
+ {props.text &&
{props.text}
}
+
+ );
+ };
+
+ const delayedShowTitle = useDelayedVisibility(GUIState.showTitle);
+ const optionData = useSelector((state: RootState) => state.userData.optionData);
+
return (
<>
- {GUIState.showTitle &&
}
+ {GUIState.showTitle &&
}
{
playBgm(GUIState.titleBgm);
dispatch(setVisibility({ component: 'isEnterGame', visibility: true }));
@@ -43,95 +74,92 @@ export default function Title() {
document.documentElement.requestFullscreen();
if (keyboard) keyboard.lock(['Escape', 'F11']);
}
+ const launchScreen = document.getElementById('launchScreen');
+ if (launchScreen) {
+ launchScreen.classList.add('launch_screen_off');
+ }
}}
onMouseEnter={playSeEnter}
/>
- {GUIState.showTitle && (
+ {delayedShowTitle && (
-
-
{
+
+
+ {titleButton({
+ text: t('start.title'),
+ icon: PlayOne,
+ onClick: () => {
startGame();
playSeClick();
- }}
- onMouseEnter={playSeEnter}
- >
-
{t('start.title')}
-
-
{
+ },
+ })}
+ {titleButton({
+ text: t('continue.title'),
+ icon: GoOn,
+ onClick: () => {
playSeClick();
dispatch(setVisibility({ component: 'showTitle', visibility: false }));
continueGame();
- }}
- onMouseEnter={playSeEnter}
- >
-
{t('continue.title')}
-
-
{
+ },
+ })}
+ {titleButton({
+ text: t('load.title'),
+ icon: FolderOpen,
+ onClick: () => {
playSeClick();
dispatch(setVisibility({ component: 'showMenuPanel', visibility: true }));
- dispatch(setMenuPanelTag(MenuPanelTag.Option));
- }}
- onMouseEnter={playSeEnter}
- >
-
{t('options.title')}
-
-
{
+ dispatch(setMenuPanelTag(MenuPanelTag.Load));
+ },
+ })}
+ {titleButton({
+ text: t('options.title'),
+ icon: SettingTwo,
+ onClick: () => {
playSeClick();
dispatch(setVisibility({ component: 'showMenuPanel', visibility: true }));
- dispatch(setMenuPanelTag(MenuPanelTag.Load));
- }}
- onMouseEnter={playSeEnter}
- >
-
{t('load.title')}
-
- {GUIState.enableAppreciationMode && (
-
{
+ dispatch(setMenuPanelTag(MenuPanelTag.Option));
+ },
+ })}
+ {GUIState.enableAppreciationMode &&
+ titleButton({
+ text: t('extra.title'),
+ icon: Star,
+ disabled: !hasAppreciationItems,
+ onClick: () => {
if (hasAppreciationItems) {
playSeClick();
dispatch(setVisibility({ component: 'showExtra', visibility: true }));
}
- }}
- onMouseEnter={playSeEnter}
- >
-
{t('extra.title')}
-
- )}
-
{
+ },
+ })}
+ {titleButton({
+ text: t('exit.title'),
+ icon: Power,
+ onClick: () => {
playSeClick();
- showGlogalDialog({
+ showGlobalDialog({
title: t('exit.tips'),
- leftText: tCommon('yes'),
- rightText: tCommon('no'),
- leftFunc: () => {
+ leftText: tCommon('cancel'),
+ rightText: tCommon('confirm'),
+ leftFunc: () => {},
+ rightFunc: () => {
window.close();
},
- rightFunc: () => {},
});
- }}
- onMouseEnter={playSeEnter}
- >
-
{t('exit.title')}
-
+ },
+ })}
)}
diff --git a/packages/webgal/src/UI/Title/title.module.scss b/packages/webgal/src/UI/Title/title.module.scss
index 0cb6e04ad..3d4192149 100644
--- a/packages/webgal/src/UI/Title/title.module.scss
+++ b/packages/webgal/src/UI/Title/title.module.scss
@@ -1,59 +1,114 @@
-.Title_main {
+.title_main {
+ position: absolute;
+ display: flex;
width: 100%;
height: 100%;
- position: absolute;
- z-index: 13;
+ align-items: flex-end;
+ opacity: 0;
+ animation: title_main_show var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+ container-type: size;
}
-.Title_buttonList {
- display: flex;
+@keyframes title_main_show {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+.title_main_hide {
+ pointer-events: none;
+ animation: title_main_hide var(--ui-transition-duration) cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+}
+
+@keyframes title_main_hide {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+
+.title_background {
position: absolute;
- left: 0;
- min-width: 25%;
+ width: 100%;
height: 100%;
- justify-content: center;
+ background-size: cover;
+ background-position: center;
+ animation: title_background_show 2s cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+}
+
+@keyframes title_background_show {
+ 0% {
+ scale: 1.1;
+ }
+ 100% {
+ scale: 1;
+ }
+}
+
+.title_button_list {
+ position: relative;
+ display: flex;
+ flex-direction: column;
align-items: flex-start;
- flex-flow: column;
- transition: background 0.75s;
- padding-left: 120px;
+ margin-left: 4vmin;
+ margin-bottom: 4vmin;
+ gap: 2vmin;
}
-.Title_button {
- font-weight: bold;
- text-align: center;
- flex: 0 1 auto;
+.title_button {
cursor: pointer;
- padding: 1em 2em 1em 2em;
- margin: 20px 0;
- transition: all 0.33s;
- background: rgba(255, 255, 255, 0.15);
- backdrop-filter: blur(5px);
- border-radius: 4px;
- transform: skewX(-10deg);
- background: linear-gradient(to right, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.1));
-}
+ position: relative;
+ z-index: 0;
+ display: flex;
+ align-items: center;
+ width: 100%;
+ height: 10vmin;
+ background-color: rgba(255, 255, 255, 0.75);
+ backdrop-filter: saturate(3) blur(3vmin);
+ box-shadow: 0 0.5vmin 0vmin rgb(0, 0, 0, 0.25);
+ transition: background-color 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+ border-radius: 3vmin 3vmin 6vmin 3vmin;
+ border-style: solid;
+ border-width: 1vmin;
+ border-color: rgba(255, 255, 255, 0.5);
+ color: rgb(45, 48, 80);
+ font-size: 3.5vmin;
+ font-weight: 500;
-.Title_button:hover {
- text-shadow: 0 0 10px rgba(255, 255, 255, 1);
- padding: 1em 6em 1em 3em;
+ &:hover {
+ transition: background-color 0s;
+ background-color: rgba(255, 255, 255, 0.9);
+ }
}
-.Title_button_text {
- font-size: 165%;
- color: #fbfbfb;
- padding: 0 0.5em 0 0.5em;
- letter-spacing: 0.2em;
+.title_button_disabled {
+ cursor: not-allowed !important;
+ pointer-events: none;
+ opacity: 0.5;
}
-.Title_backup_background {
- width: 100%;
+.title_button_icon {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
height: 100%;
- position: absolute;
- z-index: 13;
- background: linear-gradient(135deg, #fdfbfb 0%, #dcddde 100%);
+ aspect-ratio: 1 / 1;
+ line-height: 0;
+ font-size: 120%;
}
-.Title_button_disabled {
- cursor: not-allowed !important;
- opacity: 0.5;
+.title_button_text {
+ position: relative;
+ display: flex;
+ align-items: center;
+ height: 100%;
+ padding-right: 4vmin;
+ margin-right: auto;
}
diff --git a/packages/webgal/src/UI/Translation/Translation.tsx b/packages/webgal/src/UI/Translation/Translation.tsx
index ad791b5a6..5612adb68 100644
--- a/packages/webgal/src/UI/Translation/Translation.tsx
+++ b/packages/webgal/src/UI/Translation/Translation.tsx
@@ -1,14 +1,17 @@
import useLanguage from '@/hooks/useLanguage';
import { useEffect, useState } from 'react';
-import s from './translation.module.scss';
+import styles from './translation.module.scss';
import languages, { language } from '@/config/language';
import { useSelector } from 'react-redux';
import { RootState } from '@/store/store';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { useDelayedVisibility } from '@/hooks/useDelayedVisibility';
export default function Translation() {
const setLanguage = useLanguage();
const [isShowSelectLanguage, setIsShowSelectLanguage] = useState(false);
+ const delayedIsShowSelectLanguage = useDelayedVisibility(isShowSelectLanguage);
const globalVar = useSelector((state: RootState) => state.userData.globalGameVar);
const defaultLang = globalVar['Default_Language'] ?? '';
@@ -17,6 +20,8 @@ export default function Translation() {
setLanguage(langId);
};
+ const applyStyle = useApplyStyle('UI/Translation/translation.scss');
+
useEffect(() => {
const lang = window?.localStorage.getItem('lang');
if (!lang) {
@@ -53,21 +58,22 @@ export default function Translation() {
return (
<>
- {isShowSelectLanguage && (
-
-
-
LANGUAGE SELECT
-
- {Object.keys(languages).map((key) => (
-
setLang(language[key as unknown as language] as unknown as language)}
- >
- {languages[key]}
-
- ))}
-
+ {delayedIsShowSelectLanguage && (
+
+
+ {Object.keys(languages).map((key) => (
+
setLang(language[key as unknown as language] as unknown as language)}
+ >
+ {languages[key]}
+
+ ))}
)}
diff --git a/packages/webgal/src/UI/Translation/translation.module.scss b/packages/webgal/src/UI/Translation/translation.module.scss
index cc95b696d..301398d83 100644
--- a/packages/webgal/src/UI/Translation/translation.module.scss
+++ b/packages/webgal/src/UI/Translation/translation.module.scss
@@ -1,48 +1,57 @@
-.trans {
+.translation_main {
height: 100%;
width: 100%;
- background-image: linear-gradient(225deg, #a3bded 0%, #6991c7 100%);
+ background: rgba(15, 37, 64, 0.39);
+ backdrop-filter: blur(4vmin);
position: absolute;
- z-index: 20;
}
-.langWrapper{
- display: flex;
- justify-content: center;
- align-items: center;
- width: 100%;
- height: 100%;
- flex-flow: column;
+.translation_main_hide {
+ pointer-events: none;
+ animation: translation_main_hide 0.5s forwards;
}
-.lang {
- width: 100%;
- text-align: center;
- font-family: "思源宋体", serif;
- color: transparent;
- font-size: 300%;
- background: linear-gradient(150deg, rgb(255, 255, 255) 0%, rgb(255, 255, 255) 75%, #51A8DD 100%);
- -webkit-background-clip: text;
+@keyframes translation_main_hide {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
}
-.langSelect{
+.translation_button_list {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
display: flex;
- gap: 50px;
- padding: 50px;
+ flex-direction: column;
+ border-radius: 4vmin;
+ overflow: hidden;
+ gap: 2vmin;
}
-.langSelectButton{
- font-family: "思源宋体", serif;
+.translation_button {
cursor: pointer;
- font-size: 200%;
- color: #FFFFFF;
- border-radius: 4px;
- border: 1px solid rgba(255,255,255,0.8);
- padding: 10px 50px;
- transition: color 0.33s, background-color 0.33s;
-}
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 70vmin;
+ text-align: center;
+ background-color: rgb(52, 114, 185);
+ box-shadow: 0 0.5vmin 0vmin rgb(74, 105, 190);
+ border-style: solid;
+ border-width: 1vmin;
+ border-color: rgb(83, 140, 206);
+ border-radius: 5vmin;
+ color: rgb(255, 255, 255);
+ font-size: 4vmin;
+ font-weight: 500;
+ text-shadow: 0 0.3vmin 0vmin rgb(8, 76, 139);
+ padding: 1vmin 8vmin;
-.langSelectButton:hover{
- background: white;
- color: #93a5cf;
+ &:hover {
+ background-color: rgb(66, 123, 189);
+ }
}
diff --git a/packages/webgal/src/assets/logo/logo.png b/packages/webgal/src/assets/logo/logo.png
new file mode 100644
index 000000000..b97cec51a
Binary files /dev/null and b/packages/webgal/src/assets/logo/logo.png differ
diff --git a/packages/webgal/src/assets/style/animation.scss b/packages/webgal/src/assets/style/animation.scss
deleted file mode 100644
index d69a2aab5..000000000
--- a/packages/webgal/src/assets/style/animation.scss
+++ /dev/null
@@ -1,329 +0,0 @@
-@keyframes centerIn {
- 0% {
- opacity: 0;
- transform: scale(1, 1);
- }
-
- 100% {
- opacity: 1;
- transform: scale(1, 1);
- }
-}
-
-@keyframes upIn {
- 0% {
- opacity: 0;
- transform: scale(1, 1) translate(0, 3%);
- }
-
- 100% {
- opacity: 1;
- transform: scale(1, 1) translate(0, 0);
- }
-}
-
-@keyframes leftIn {
- 0% {
- opacity: 0;
- transform: scale(1, 1) translate(-3%, 0);
- }
-
- 100% {
- opacity: 1;
- transform: scale(1, 1) translate(0, 0);
- }
-}
-
-@keyframes rightIn {
- 0% {
- opacity: 0;
- transform: scale(1, 1) translate(3%, 0);
- }
-
- 100% {
- opacity: 1;
- transform: scale(1, 1) translate(0, 0);
- }
-}
-
-@keyframes bg_down {
- 0% {
- opacity: 0;
- transform: scale(1.1, 1.1) translate(0, -3%);
- }
-
- 100% {
- opacity: 1;
- transform: scale(1, 1) translate(0, 0);
- }
-}
-
-@keyframes bg_softIn {
- 0% {
- opacity: 0;
- }
-
- 100% {
- opacity: 1;
- }
-}
-
-@keyframes hideBG {
- 0% {
- opacity: 1;
- }
-
- 100% {
- opacity: 0;
- }
-}
-
-@keyframes shake {
- 0% {
- transform: translate(0, 0);
- }
-
- 25% {
- transform: translate(-2%, 0);
- }
-
- 75% {
- transform: translate(2%, 0);
- }
-
- 100% {
- transform: translate(0, 0);
- }
-}
-
-@keyframes moveBaF {
- 0% {
- transform: scale(1, 1);
- }
-
- 50% {
- transform: scale(1.1, 1.1);
- }
-
- 100% {
- transform: scale(1, 1);
- }
-}
-
-@keyframes showSoftly /* Safari 与 Chrome */
-{
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
-}
-
-@keyframes elementFadeIn {
- 0% {
- transform: translate(-15px, -20px) scale(1.03, 1.03);
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
-}
-
-@keyframes controlButtonHover {
- 0% {
- background-color: rgba(0, 0, 0, 0);
- box-shadow: none;
- }
- 100% {
- background-color: rgba(255, 255, 255, 0.25);
- box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.65);
- }
-}
-
-@keyframes controlButtonHoverBack {
- 0% {
- /*background-color: rgba(255, 255, 255, 0.25);*/
- /*box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.65);*/
- }
- 100% {
-
- }
-}
-
-@keyframes TitleButtonOnChoose {
- 0% {
- transform: scale(1, 1);
- /*background-color: rgba(255,255,255,0.8);*/
- }
-
- 100% {
- /*box-shadow: 3px 3px 15px rgba(0, 0, 0, 0.5);*/
- transform: scale(1.1, 1.1);
- /*background-color: rgba(0,0,0,0.15);*/
- }
-}
-
-@keyframes TitleButtonNoneChoose {
- 0% {
- /*box-shadow: 3px 3px 15px rgba(0, 0, 0, 0.5);*/
- transform: scale(1.1, 1.1);
- /*background-color: rgba(0,0,0,0.15);*/
- }
-
- 100% {
-
- }
-}
-
-@keyframes TitleModelHover {
- 0% {
- background-color: rgba(0, 0, 0, 0.35);
- }
- 100% {
- background-color: rgba(0, 0, 0, 0.65);
- }
-}
-
-@keyframes TitleModelNoneHover {
- 0% {
- background-color: rgba(0, 0, 0, 0.65);
- }
- 100% {
- background-color: rgba(0, 0, 0, 0.35);
- }
-}
-
-
-/*背景的演出效果*/
-@keyframes bg_focusLeft {
- 0% {
- transform: scale(1, 1) translate(0, 0);
- filter: blur(0);
- }
- 100% {
- transform: scale(1.15, 1.15) translate(5%, 0);
- filter: blur(1px);
- }
-}
-
-@keyframes bg_focusRight {
- 0% {
- transform: scale(1, 1) translate(0, 0);
- filter: blur(0);
- }
- 100% {
- transform: scale(1.15, 1.15) translate(-5%, 0);
- filter: blur(1px);
- }
-}
-
-@keyframes bg_LtoR {
- 0% {
- transform: scale(1.15, 1.15) translate(5%, 0);
- filter: blur(1px);
- }
- 100% {
- transform: scale(1.15, 1.15) translate(-5%, 0);
- filter: blur(1px);
- }
-}
-
-@keyframes bg_RtoL {
- 0% {
- transform: scale(1.15, 1.15) translate(-5%, 0);
- filter: blur(1px);
- }
- 100% {
- transform: scale(1.15, 1.15) translate(5%, 0);
- filter: blur(1px);
- }
-}
-
-@keyframes bg_LtoC {
- 0% {
- transform: scale(1.15, 1.15) translate(5%, 0);
- filter: blur(1px);
- }
- 100% {
- transform: scale(1, 1) translate(0, 0);
- filter: blur(0);
- }
-}
-
-@keyframes bg_RtoC {
- 0% {
- transform: scale(1.15, 1.15) translate(-5%, 0);
- filter: blur(1px);
- }
- 100% {
- transform: scale(1, 1) translate(0, 0);
- filter: blur(0);
- }
-}
-
-@keyframes bg_focus {
- 0% {
- transform: scale(1, 1) translate(0, 0);
- filter: blur(0);
- }
- 100% {
- transform: scale(1.15, 1.15);
- filter: blur(1px);
- }
-}
-
-@keyframes bg_LtoF {
- 0% {
- transform: scale(1.15, 1.15) translate(5%, 0);
- filter: blur(1px);
- }
- 100% {
- transform: scale(1.15, 1.15);
- filter: blur(1px);
- }
-}
-
-@keyframes bg_RtoF {
- 0% {
- transform: scale(1.15, 1.15) translate(-5%, 0);
- filter: blur(1px);
- }
- 100% {
- transform: scale(1.15, 1.15);
- filter: blur(1px);
- }
-}
-
-@keyframes bg_FtoL {
- 0% {
- transform: scale(1.15, 1.15);
- filter: blur(1px);
- }
- 100% {
- transform: scale(1.15, 1.15) translate(5%, 0);
- filter: blur(1px);
- }
-}
-
-@keyframes bg_FtoR {
- 0% {
- transform: scale(1.15, 1.15);
- filter: blur(1px);
- }
- 100% {
- transform: scale(1.15, 1.15) translate(-5%, 0);
- filter: blur(1px);
- }
-}
-
-@keyframes bg_FtoC {
- 0% {
- transform: scale(1.15, 1.15);
- filter: blur(1px);
- }
- 100% {
- transform: scale(1, 1) translate(0, 0);
- filter: blur(0);
- }
-}
diff --git a/packages/webgal/src/hooks/useConfigData.ts b/packages/webgal/src/hooks/useConfigData.ts
index 830976794..4cdf2106e 100644
--- a/packages/webgal/src/hooks/useConfigData.ts
+++ b/packages/webgal/src/hooks/useConfigData.ts
@@ -1,6 +1,5 @@
import { getFastSaveFromStorage, getSavesFromStorage } from '@/Core/controller/storage/savesController';
import { getStorage } from '@/Core/controller/storage/storageController';
-import { setEbg } from '@/Core/gameScripts/changeBg/setEbg';
import { assetSetter, fileType } from '@/Core/util/gameAssetsAccess/assetSetter';
import { WebGAL } from '@/Core/WebGAL';
import { setGuiAsset, setLogoImage } from '@/store/GUIReducer';
@@ -22,7 +21,6 @@ const useConfigData = () => {
case 'Title_img': {
const titleUrl = assetSetter(val, fileType.background);
webgalStore.dispatch(setGuiAsset({ asset: 'titleBg', value: titleUrl }));
- setEbg(titleUrl);
break;
}
diff --git a/packages/webgal/src/hooks/useDelayedVisibility.ts b/packages/webgal/src/hooks/useDelayedVisibility.ts
new file mode 100644
index 000000000..759679de5
--- /dev/null
+++ b/packages/webgal/src/hooks/useDelayedVisibility.ts
@@ -0,0 +1,30 @@
+import { RootState } from '@/store/store';
+import { useEffect, useRef, useState } from 'react';
+import { useSelector } from 'react-redux';
+
+// 延迟退场状态管理 Hook
+export function useDelayedVisibility(visible: boolean): boolean {
+ const uiTransitionDuration = useSelector((state: RootState) => state.userData.optionData.uiTransitionDuration);
+ const [delayedVisible, setDelayedVisible] = useState(visible);
+ const timeoutRef = useRef
| null>(null);
+
+ useEffect(() => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ if (visible) {
+ setDelayedVisible(true);
+ } else {
+ timeoutRef.current = setTimeout(() => {
+ setDelayedVisible(false);
+ }, uiTransitionDuration);
+ }
+ return () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ };
+ }, [visible, uiTransitionDuration]);
+
+ return delayedVisible;
+}
diff --git a/packages/webgal/src/hooks/useHotkey.tsx b/packages/webgal/src/hooks/useHotkey.tsx
index 868f738a6..42a43f280 100644
--- a/packages/webgal/src/hooks/useHotkey.tsx
+++ b/packages/webgal/src/hooks/useHotkey.tsx
@@ -9,11 +9,13 @@ import { componentsVisibility, MenuPanelTag } from '@/store/guiInterface';
import { setVisibility } from '@/store/GUIReducer';
import { RootState } from '@/store/store';
import { setOptionData } from '@/store/userDataReducer';
-import styles from '@/UI/Backlog/backlog.module.scss';
+import styles from '@/Stage/Backlog/backlog.module.scss';
import throttle from 'lodash/throttle';
import { useCallback, useEffect, useRef } from 'react';
import { useDispatch } from 'react-redux';
import useFullScreen from './useFullScreen';
+import { setTime } from '~build/time';
+import { stopAuto } from '@/Core/controller/gamePlay/autoPlay';
// options备用
export interface HotKeyType {
@@ -123,6 +125,7 @@ export function useMouseWheel() {
const ctrlKey = ev.ctrlKey;
const dom = document.querySelector(`.${styles.backlog_content}`);
if (isGameActive() && direction === 'up' && !ctrlKey) {
+ stopAll();
setComponentVisibility('showBacklog', true);
setComponentVisibility('showTextBox', false);
} else if (isInBackLog() && direction === 'down' && !ctrlKey) {
@@ -138,6 +141,7 @@ export function useMouseWheel() {
}
// setComponentVisibility('showBacklog', false);
} else if (isGameActive() && direction === 'down' && !ctrlKey) {
+ stopAuto();
clearTimeout(wheelTimeout);
WebGAL.gameplay.isFast = true;
// 滚轮视作快进
@@ -198,17 +202,17 @@ export function useSkip() {
const isCtrlKey = useCallback((e) => e.keyCode === 17, []);
const handleCtrlKeydown = useCallback((e) => {
if (isCtrlKey(e) && isGameActive()) {
+ stopAuto();
startFast();
}
}, []);
const handleCtrlKeyup = useCallback((e) => {
if (isCtrlKey(e) && isGameActive()) {
- stopFast();
+ stopAll();
}
}, []);
const handleWindowBlur = useCallback((e) => {
- // 停止快进
- stopFast();
+ stopAll();
}, []);
// mounted时绑定事件
useMounted(() => {
@@ -225,7 +229,7 @@ export function useSkip() {
// updated时验证状态
useUpdated(() => {
if (!isGameActive()) {
- stopFast();
+ stopAll();
}
});
}
@@ -385,7 +389,14 @@ function useToggleFullScreen() {
const dispatch = useDispatch();
useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => e.repeat || (e.key === 'F11' && toggle());
+ const handleKeyDown = (e: KeyboardEvent) => {
+ switch (e.key) {
+ case 'F11':
+ e.preventDefault();
+ toggle();
+ break;
+ }
+ };
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
diff --git a/packages/webgal/src/index.scss b/packages/webgal/src/index.scss
index 4848007a2..16ad83c43 100644
--- a/packages/webgal/src/index.scss
+++ b/packages/webgal/src/index.scss
@@ -17,6 +17,22 @@
src: url('assets/fonts/OPPOSans-R.ttf') format('truetype');
}
+.StartButton {
+ animation: StartButton_blink 4s infinite;
+}
+
+@keyframes StartButton_blink {
+ 0% {
+ text-shadow: 0 0 15px rgba(0, 0, 0, 0.65);
+ }
+ 50% {
+ text-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
+ }
+ 100% {
+ text-shadow: 0 0 15px rgba(0, 0, 0, 0.65);
+ }
+}
+
a {
transition: color 1s;
}
@@ -37,12 +53,38 @@ a:active {
color: #434343;
}
+html {
+ overflow: hidden;
+ position: absolute;
+ width: 100vw;
+ height: 100vh;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ transform-origin: center center;
+ transition: transform 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+}
+
+// @media (max-aspect-ratio: 1/1) {
+// html {
+// width: 100vh;
+// height: 100vw;
+// transform: translate(-50%, -50%) rotate(90deg);
+// }
+// }
+
+.html_rotate {
+ width: 100vh;
+ height: 100vw;
+ transform: translate(-50%, -50%) rotate(90deg);
+}
+
body {
background-color: #000;
- font-size: 16px;
margin: 0;
- font-family: 'WebgalUI', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
- 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+ font-family: "WebgalUI", -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow: hidden;
@@ -50,35 +92,149 @@ body {
}
code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
}
-#root {
- width: $screenWidth;
- height: $screenHeight;
+.launch_screen {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ height: 100%;
overflow: hidden;
- font-size: 160%;
+ position: absolute;
+ background-color: #000000;
+ z-index: 14;
+}
+
+.launch_screen_off {
+ pointer-events: none;
+ animation: launch_screen_fade_out 0.5s cubic-bezier(0.215, 0.610, 0.355, 1) forwards;
+}
+
+@keyframes launch_screen_fade_out {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ display: none;
+ }
+}
+
+.launch_screen_logo_container {
+ position: absolute;
+ top: 20%;
+ left: 20%;
+ right: 20%;
+ bottom: 30%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.launch_screen_logo {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ pointer-events: none;
+}
+
+
+.launch_screen_loading_container {
+ position: absolute;
+ bottom: 15%;
+ height: auto;
+ // opacity: 0;
+ // 出于观感
+ // 延迟一段时间再显示加载条
+ // animation: launch_screen_loading_fade_in 0.5s ease-in-out 1s forwards;
+ // background-color: #434343;
+ display: flex;
+ justify-content: center;
+ max-width: 512px;
+ width: 90%;
+ justify-self: center;
+ transform: translateY(50%);
+}
+
+@keyframes launch_screen_loading_fade_in {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+.launch_screen_loading_bar {
+ height: 1vmin;
+ width: 0%;
+ border-radius: 0.5vmin;
+ background-color: rgb(255, 255, 255);
+ transition: width 0.5s ease-out;
+ font-size: 0vmin;
+ font-weight: bolder;
+ text-align: center;
+ align-content: center;
+ transition: width 0.5s ease-out;
+}
+
+.launch_screen_loading_bar_loaded {
+ transition: 0.5s cubic-bezier(0.215, 0.610, 0.355, 1);
+ width: 32vmin;
+ height: 8vmin;
+ border-radius: 4vmin;
+ padding: 0 4vmin 0 4vmin;
+ font-size: 4vmin;
+ cursor: pointer;
+ animation: launch_screen_loading_bar_highlight 1s ease-out infinite;
+}
+
+@keyframes launch_screen_loading_bar_highlight {
+ 0% {
+ scale: 1;
+ }
+ 50% {
+ scale: 1;
+ }
+ 70% {
+ scale: 1.1;
+ }
+ 100% {
+ scale: 1;
+ }
}
/* 设置滚动条的样式 */
::-webkit-scrollbar {
- width: 12px;
+ background-color: transparent;
+ width: 2vmin;
+ height: 2vmin;
}
/* 滚动槽 */
::-webkit-scrollbar-track {
- box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
- -webkit-box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
- background: rgba(255, 255, 255, 0.2);
- border-radius: 10px;
+ background-color: transparent;
+}
+
+::-webkit-scrollbar-corner {
+ background-color: transparent;
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
- border-radius: 10px;
- background: rgba(255, 255, 255, 0.5);
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
- -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
+ background-color: rgba(128, 128, 128, 0.5);
+ // background-color: rgba(0, 0, 0, 0.25);
+ border-radius: 1vmin;
+}
+
+#root {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
}
.App {
@@ -89,7 +245,3 @@ code {
perspective: 0;
-webkit-overflow-scrolling: auto;
}
-
-#pixiCanvas {
- z-index: 5;
-}
diff --git a/packages/webgal/src/main.tsx b/packages/webgal/src/main.tsx
index 3a18f4117..25c345ca8 100644
--- a/packages/webgal/src/main.tsx
+++ b/packages/webgal/src/main.tsx
@@ -1,8 +1,6 @@
import { StrictMode } from 'react';
import ReactDOM from 'react-dom';
import App from './App';
-import './index.scss';
-import './assets/style/animation.scss';
import 'modern-css-reset/dist/reset.min.css';
/** i18n */
diff --git a/packages/webgal/src/store/stageInterface.ts b/packages/webgal/src/store/stageInterface.ts
index a9606d89c..6003bfbc5 100644
--- a/packages/webgal/src/store/stageInterface.ts
+++ b/packages/webgal/src/store/stageInterface.ts
@@ -1,5 +1,6 @@
import { ISentence } from '@/Core/controller/scene/sceneInterface';
import { BlinkParam, FocusParam } from '@/Core/live2DCore';
+import { textSize } from './userDataInterface';
/**
* 游戏内变量
@@ -186,7 +187,7 @@ export interface IStageState {
freeFigure: Array;
figureAssociatedAnimation: Array;
showText: string; // 文字
- showTextSize: number; // 文字
+ showTextSize: textSize; // 文字
showName: string; // 人物名
command: string; // 语句指令
choose: Array; // 选项列表
diff --git a/packages/webgal/src/store/stageReducer.ts b/packages/webgal/src/store/stageReducer.ts
index a764af4cf..914d0a5b8 100644
--- a/packages/webgal/src/store/stageReducer.ts
+++ b/packages/webgal/src/store/stageReducer.ts
@@ -21,6 +21,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import cloneDeep from 'lodash/cloneDeep';
import { commandType } from '@/Core/controller/scene/sceneInterface';
import { STAGE_KEYS } from '@/Core/constants';
+import { textSize } from './userDataInterface';
// 初始化舞台数据
@@ -33,7 +34,7 @@ export const initState: IStageState = {
freeFigure: [],
figureAssociatedAnimation: [],
showText: '', // 文字
- showTextSize: -1,
+ showTextSize: textSize.default,
showName: '', // 人物名
command: '', // 语句指令
choose: [], // 选项列表,现在不用,先预留
diff --git a/packages/webgal/src/store/userDataInterface.ts b/packages/webgal/src/store/userDataInterface.ts
index e627b1f37..9ca689bd0 100644
--- a/packages/webgal/src/store/userDataInterface.ts
+++ b/packages/webgal/src/store/userDataInterface.ts
@@ -13,6 +13,7 @@ export enum playSpeed {
}
export enum textSize {
+ default,
small,
medium,
large,
@@ -34,6 +35,24 @@ export enum fullScreenOption {
off,
}
+// 钉固控制面板按钮
+export enum pinnedControlPanelButton {
+ none = 0,
+ save = 1 << 0,
+ load = 1 << 1,
+ quickSave = 1 << 2,
+ quickLoad = 1 << 3,
+ autoPlay = 1 << 4,
+ fastForward = 1 << 5,
+ title = 1 << 6,
+ options = 1 << 7,
+ backlog = 1 << 8,
+ replay = 1 << 9,
+ hideTextbox = 1 << 10,
+ fullScreen = 1 << 11,
+ lock = 1 << 12,
+}
+
/**
* @interface IOptionData 用户设置数据接口
*/
@@ -52,6 +71,8 @@ export interface IOptionData {
language: language;
voiceInterruption: voiceOption; // 是否中断语音
fullScreen: fullScreenOption;
+ uiTransitionDuration: number; // 用户界面切换持续时间
+ pinnedControlPanelButtons: pinnedControlPanelButton; // 钉固控制面板按钮
}
/**
diff --git a/packages/webgal/src/store/userDataReducer.ts b/packages/webgal/src/store/userDataReducer.ts
index bd80b9146..652a9fdd6 100644
--- a/packages/webgal/src/store/userDataReducer.ts
+++ b/packages/webgal/src/store/userDataReducer.ts
@@ -12,6 +12,7 @@ import {
ISetUserDataPayload,
IUserData,
fullScreenOption,
+ pinnedControlPanelButton,
playSpeed,
textFont,
textSize,
@@ -36,6 +37,8 @@ const initialOptionSet: IOptionData = {
language: language.zhCn,
voiceInterruption: voiceOption.yes,
fullScreen: fullScreenOption.off,
+ uiTransitionDuration: 250,
+ pinnedControlPanelButtons: pinnedControlPanelButton.none,
};
// 初始化用户数据
diff --git a/packages/webgal/src/translations/de.ts b/packages/webgal/src/translations/de.ts
index 3af3014eb..e5a783d42 100644
--- a/packages/webgal/src/translations/de.ts
+++ b/packages/webgal/src/translations/de.ts
@@ -3,6 +3,8 @@ const de = {
common: {
yes: 'Ja',
no: 'Nein',
+ confirm: 'Bestätigen',
+ cancel: 'Abbrechen',
},
menu: {
@@ -63,6 +65,16 @@ const de = {
display: {
title: 'Darstellung',
options: {
+ fullScreen: {
+ title: 'Vollbildmodus',
+ options: {
+ on: 'Aktiviert',
+ off: 'Deaktiviert',
+ },
+ },
+ uiTransitionDuration: {
+ title: 'UI-Übergangsdauer',
+ },
textSpeed: {
title: 'Geschwindigkeit der Textanzeige',
options: {
@@ -104,6 +116,26 @@ const de = {
bgmVolume: { title: 'Musiklautstärke' },
seVolume: { title: 'Soundeffektlautstärke' },
uiSeVolume: { title: 'UI Soundeffektlautstärke' },
+ voiceInterruption: {
+ title: 'Sprachunterbrechung',
+ options: {
+ voiceStop: 'Sprache stoppen',
+ voiceContinue: 'Sprache fortsetzen',
+ },
+ },
+ },
+ },
+ about: {
+ title: 'Über',
+ options: {
+ webgal: {
+ title: 'WebGAL',
+ subTitle: 'WebGAL: Eine Open-Source Web-Based Visual Novel Engine',
+ version: 'Version',
+ source: 'Source Code Repository',
+ contributors: 'Contributors',
+ website: 'Website',
+ },
},
},
// language: {
@@ -169,14 +201,19 @@ const de = {
quicklyLoad: 'Quickly Load',
save: 'Speichern',
load: 'Laden',
- fullscrreen: 'Vollbild',
+ fullscreen: 'Vollbild',
options: 'Optionen',
title: 'Titel',
+ titleTips: 'Sind Sie sicher, dass Sie zum Titelbildschirm zurückkehren möchten?',
+ lock: 'Sperren',
},
},
extra: {
title: 'EXTRA',
+ cg: 'CG',
+ bgm: 'BGM',
+ defaultSeries: 'Standardserie',
},
};
diff --git a/packages/webgal/src/translations/en.ts b/packages/webgal/src/translations/en.ts
index 0fb52e5c7..703b9a125 100644
--- a/packages/webgal/src/translations/en.ts
+++ b/packages/webgal/src/translations/en.ts
@@ -3,6 +3,8 @@ const en = {
common: {
yes: 'OK',
no: 'Cancel',
+ confirm: 'Confirm',
+ cancel: 'Cancel',
},
menu: {
@@ -50,14 +52,6 @@ const en = {
},
},
},
- about: {
- title: 'About WebGAL',
- subTitle: 'WebGAL: An Open-Source Web-Based Visual Novel Engine',
- version: 'Version',
- source: 'Source Code Repository',
- contributors: 'Contributors',
- website: 'Website',
- },
},
},
display: {
@@ -70,6 +64,9 @@ const en = {
off: 'OFF',
},
},
+ uiTransitionDuration: {
+ title: 'UI Transition Duration',
+ },
textSpeed: {
title: 'Text Speed',
options: {
@@ -111,6 +108,26 @@ const en = {
bgmVolume: { title: 'BGM Volume' },
seVolume: { title: 'Sound Effects Volume' },
uiSeVolume: { title: 'UI Sound Effects Volume' },
+ voiceInterruption: {
+ title: 'Voice Interruption',
+ options: {
+ voiceStop: 'Stop Voice',
+ voiceContinue: 'Continue Voice',
+ },
+ },
+ },
+ },
+ about: {
+ title: 'About',
+ options: {
+ webgal: {
+ title: 'WebGAL',
+ subTitle: 'WebGAL: An Open-Source Web-Based Visual Novel Engine',
+ version: 'Version',
+ source: 'Source Code Repository',
+ contributors: 'Contributors',
+ website: 'Website',
+ },
},
},
// language: {
@@ -180,11 +197,15 @@ const en = {
options: 'Options',
title: 'Title',
titleTips: 'Confirm return to the title screen',
+ lock: 'Lock',
},
},
extra: {
title: 'EXTRA',
+ cg: 'CG',
+ bgm: 'BGM',
+ defaultSeries: 'Default Series',
},
};
diff --git a/packages/webgal/src/translations/fr.ts b/packages/webgal/src/translations/fr.ts
index ffbc6ec57..6e5bb0481 100644
--- a/packages/webgal/src/translations/fr.ts
+++ b/packages/webgal/src/translations/fr.ts
@@ -3,6 +3,8 @@ const fr = {
common: {
yes: 'OK',
no: 'Annuler',
+ confirm: 'Confirmer',
+ cancel: 'Annuler',
},
menu: {
@@ -50,19 +52,21 @@ const fr = {
},
},
},
- about: {
- title: 'À propos de WebGAL',
- subTitle: 'WebGAL: Un moteur de visual novel basé sur le web en open-source',
- version: 'Version',
- source: 'Dépôt de code source',
- contributors: 'Contributeurs',
- website: 'Site web',
- },
},
},
display: {
title: 'Affichage',
options: {
+ fullScreen: {
+ title: 'Mode plein écran',
+ options: {
+ on: 'Activé',
+ off: 'Désactivé',
+ },
+ },
+ uiTransitionDuration: {
+ title: 'Durée de transition de l’interface',
+ },
textSpeed: {
title: "Vitesse d'affichage du texte",
options: {
@@ -104,6 +108,26 @@ const fr = {
bgmVolume: { title: 'Volume de la musique de fond' },
seVolume: { title: 'Volume des effets sonores' },
uiSeVolume: { title: 'Volume de l’interface utilisateur' },
+ voiceInterruption: {
+ title: 'Interruption de la voix',
+ options: {
+ voiceStop: 'Arrêter la voix',
+ voiceContinue: 'Continuer la voix',
+ },
+ },
+ },
+ },
+ about: {
+ title: 'À propos',
+ options: {
+ webgal: {
+ title: 'WebGAL',
+ subTitle: 'WebGAL: Un moteur de visual novel basé sur le web en open-source',
+ version: 'Version',
+ source: 'Dépôt de code source',
+ contributors: 'Contributeurs',
+ website: 'Site web',
+ },
},
},
// language: {
@@ -169,15 +193,19 @@ const fr = {
quicklyLoad: 'Chargement rapide',
save: 'Sauvegarder',
load: 'Charger',
- fullscrren: 'Plein écran',
+ fullscreen: 'Plein écran',
options: 'Options',
title: 'Titre',
titleTips: "Confirmer le retour à l'écran titre ?",
+ lock: 'Verrouiller',
},
},
extra: {
title: 'EXTRA',
+ cg: 'CG',
+ bgm: 'BGM',
+ defaultSeries: 'Série par défaut',
},
};
diff --git a/packages/webgal/src/translations/jp.ts b/packages/webgal/src/translations/jp.ts
index 4bfcf8312..9e67727e8 100644
--- a/packages/webgal/src/translations/jp.ts
+++ b/packages/webgal/src/translations/jp.ts
@@ -3,6 +3,8 @@ const jp = {
common: {
yes: 'はい',
no: 'いいえ',
+ confirm: '確認',
+ cancel: 'キャンセル',
},
menu: {
@@ -50,14 +52,6 @@ const jp = {
},
},
},
- about: {
- title: 'WebGAL について',
- subTitle: 'WebGAL: オープンソースのウェブベースビジュアルノベルエンジン',
- version: 'バージョン',
- source: 'ソースコードリポジトリ',
- contributors: '貢献者',
- website: 'ウェブサイト',
- },
},
},
display: {
@@ -70,6 +64,9 @@ const jp = {
off: 'オフ',
},
},
+ uiTransitionDuration: {
+ title: 'UI遷移の持続時間',
+ },
textSpeed: {
title: 'テキスト表示速度',
options: {
@@ -90,7 +87,7 @@ const jp = {
title: 'フォント',
options: {
siYuanSimSun: '源ノ明朝(中国語)',
- SimHei: 'OPPO Sans',
+ SimHei: '源ノ角ゴシック(中国語)',
lxgw: 'LXGW WenKai',
},
},
@@ -111,9 +108,26 @@ const jp = {
bgmVolume: { title: 'BGM 音量' },
seVolume: { title: '効果音音量' },
uiSeVolume: { title: 'UI 効果音音量' },
- voiceOption: { title: 'ボイスの中断' },
- voiceStop: { title: '中断する' },
- voiceContinue: { title: '中断しない' },
+ voiceInterruption: {
+ title: '音声の中断',
+ options: {
+ voiceStop: '音声を停止',
+ voiceContinue: '音声を再開',
+ },
+ },
+ },
+ },
+ about: {
+ title: 'について',
+ options: {
+ webgal: {
+ title: 'WebGAL',
+ subTitle: 'WebGAL: オープンソースのウェブベースビジュアルノベルエンジン',
+ version: 'バージョン',
+ source: 'ソースコードリポジトリ',
+ contributors: '貢献者',
+ website: 'ウェブサイト',
+ },
},
},
// language: {
@@ -179,15 +193,19 @@ const jp = {
quicklyLoad: 'QUICK LOAD',
save: 'SAVE',
load: 'LOAD',
- fullscrren: 'FULLSCREEN',
+ fullscreen: 'FULLSCREEN',
options: 'CONFIG',
title: 'HOME',
titleTips: 'タイトル画面に戻りますか?',
+ lock: 'LOCK',
},
},
extra: {
title: '鑑賞モード',
+ cg: 'CG',
+ bgm: 'BGM',
+ defaultSeries: 'デフォルトシリーズ',
},
};
diff --git a/packages/webgal/src/translations/zh-cn.ts b/packages/webgal/src/translations/zh-cn.ts
index 0a380fbdb..662a387a7 100644
--- a/packages/webgal/src/translations/zh-cn.ts
+++ b/packages/webgal/src/translations/zh-cn.ts
@@ -3,6 +3,8 @@ const zhCn = {
common: {
yes: '是',
no: '否',
+ confirm: '确认',
+ cancel: '取消',
},
menu: {
@@ -50,14 +52,6 @@ const zhCn = {
},
},
},
- about: {
- title: '关于 WebGAL',
- subTitle: 'WebGAL:开源的网页端视觉小说引擎',
- version: '版本号',
- source: '源代码仓库',
- contributors: '贡献者',
- website: '网站',
- },
},
},
display: {
@@ -70,6 +64,9 @@ const zhCn = {
off: '关闭',
},
},
+ uiTransitionDuration: {
+ title: '界面切换持续时间',
+ },
textSpeed: {
title: '文字显示速度',
options: {
@@ -111,9 +108,26 @@ const zhCn = {
bgmVolume: { title: '背景音乐音量' },
seVolume: { title: '音效音量' },
uiSeVolume: { title: '用户界面音效音量' },
- voiceOption: { title: '是否中断语音' },
- voiceStop: { title: '停止语音' },
- voiceContinue: { title: '继续语音' },
+ voiceInterruption: {
+ title: '是否中断语音',
+ options: {
+ voiceStop: '停止语音',
+ voiceContinue: '继续语音',
+ },
+ },
+ },
+ },
+ about: {
+ title: '关于',
+ options: {
+ webgal: {
+ title: 'WebGAL',
+ subTitle: 'WebGAL:开源的网页端视觉小说引擎',
+ version: '版本号',
+ source: '源代码仓库',
+ contributors: '贡献者',
+ website: '网站',
+ },
},
},
// language: {
@@ -187,11 +201,15 @@ const zhCn = {
options: '选项',
title: '标题',
titleTips: '确定要返回标题界面吗?',
+ lock: '锁定',
},
},
extra: {
title: '鉴赏模式',
+ cg: '图片',
+ bgm: '音乐',
+ defaultSeries: '默认系列',
},
};
diff --git a/packages/webgal/src/translations/zh-tw.ts b/packages/webgal/src/translations/zh-tw.ts
index fa6208c16..f6d6ac6e2 100644
--- a/packages/webgal/src/translations/zh-tw.ts
+++ b/packages/webgal/src/translations/zh-tw.ts
@@ -3,6 +3,8 @@ const zhTw = {
common: {
yes: '是',
no: '否',
+ confirm: '確認',
+ cancel: '取消',
},
menu: {
@@ -50,14 +52,6 @@ const zhTw = {
},
},
},
- about: {
- title: '關於 WebGAL',
- subTitle: 'WebGAL:開源的線上視覺小說製作引擎',
- version: '版本號',
- source: '原始碼倉庫',
- contributors: '貢獻者',
- website: '網站',
- },
},
},
display: {
@@ -116,6 +110,19 @@ const zhTw = {
voiceContinue: { title: '繼續語音' },
},
},
+ about: {
+ title: '關於',
+ options: {
+ webgal: {
+ title: 'WebGAL',
+ subTitle: 'WebGAL:開源的網頁端視覺小說引擎',
+ version: '版本號',
+ source: '源代碼倉庫',
+ contributors: '貢獻者',
+ website: '網站',
+ },
+ },
+ },
// language: {
// title: '語言',
// options: {
@@ -152,11 +159,11 @@ const zhTw = {
subtitle: 'CONTINUE',
},
options: {
- title: '遊戲設定',
+ title: '設定',
subtitle: 'OPTIONS',
},
load: {
- title: '讀取存檔',
+ title: '讀檔',
subtitle: 'LOAD',
},
extra: {
@@ -187,11 +194,15 @@ const zhTw = {
options: '設定',
title: '主選單',
titleTips: '確定要返回主選單嗎?',
+ lock: '鎖定',
},
},
extra: {
title: 'CG模式',
+ cg: '圖片',
+ bgm: '音樂',
+ defaultSeries: '默認系列',
},
};