Commit afc1687d authored by timel's avatar timel

Merge branch 'dev/module-css' into 'main'

Dev/module css

See merge request mycard/Neos!249
parents a754c2c2 59b3f4d9
...@@ -24,3 +24,7 @@ ...@@ -24,3 +24,7 @@
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
# scss type
*.module.scss.d.ts
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
"sass": "^1.61.0", "sass": "^1.61.0",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vite": "^4.2.1", "vite": "^4.2.1",
"vite-plugin-sass-dts": "^1.3.8",
"vite-plugin-wasm-pack": "^0.1.12", "vite-plugin-wasm-pack": "^0.1.12",
"vite-tsconfig-paths": "^4.0.8", "vite-tsconfig-paths": "^4.0.8",
"vite-ydk-loader": "^0.0.2" "vite-ydk-loader": "^0.0.2"
...@@ -6329,6 +6330,15 @@ ...@@ -6329,6 +6330,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"engines": {
"node": ">= 6"
}
},
"node_modules/caniuse-api": { "node_modules/caniuse-api": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
...@@ -17939,6 +17949,25 @@ ...@@ -17939,6 +17949,25 @@
"url": "https://opencollective.com/postcss/" "url": "https://opencollective.com/postcss/"
} }
}, },
"node_modules/postcss-js": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dev": true,
"dependencies": {
"camelcase-css": "^2.0.1"
},
"engines": {
"node": "^12 || ^14 || >= 16"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.4.21"
}
},
"node_modules/postcss-lab-function": { "node_modules/postcss-lab-function": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz",
...@@ -26652,6 +26681,24 @@ ...@@ -26652,6 +26681,24 @@
} }
} }
}, },
"node_modules/vite-plugin-sass-dts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/vite-plugin-sass-dts/-/vite-plugin-sass-dts-1.3.8.tgz",
"integrity": "sha512-d6nHXyvYlFaZfO651SRR3PpYqOHFgetQ1WFPq4DZtPVABtSpEBUu3Qt3KE4+i1BLdPnhOvmXPDVvMFqfGaQYNA==",
"dev": true,
"dependencies": {
"postcss-js": "^4.0.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
"postcss": "^8",
"prettier": "^2.7 || ^3",
"sass": "*",
"vite": "^3 || ^4"
}
},
"node_modules/vite-plugin-svgr": { "node_modules/vite-plugin-svgr": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-2.4.0.tgz", "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-2.4.0.tgz",
...@@ -33221,6 +33268,12 @@ ...@@ -33221,6 +33268,12 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="
}, },
"camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true
},
"caniuse-api": { "caniuse-api": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
...@@ -42258,6 +42311,15 @@ ...@@ -42258,6 +42311,15 @@
} }
} }
}, },
"postcss-js": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dev": true,
"requires": {
"camelcase-css": "^2.0.1"
}
},
"postcss-lab-function": { "postcss-lab-function": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz",
...@@ -48969,6 +49031,15 @@ ...@@ -48969,6 +49031,15 @@
"rollup": "^3.18.0" "rollup": "^3.18.0"
} }
}, },
"vite-plugin-sass-dts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/vite-plugin-sass-dts/-/vite-plugin-sass-dts-1.3.8.tgz",
"integrity": "sha512-d6nHXyvYlFaZfO651SRR3PpYqOHFgetQ1WFPq4DZtPVABtSpEBUu3Qt3KE4+i1BLdPnhOvmXPDVvMFqfGaQYNA==",
"dev": true,
"requires": {
"postcss-js": "^4.0.1"
}
},
"vite-plugin-svgr": { "vite-plugin-svgr": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-2.4.0.tgz", "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-2.4.0.tgz",
.card-modal-desc { .desc {
line-height: 1.6; line-height: 1.6;
font-size: 14px; font-size: 14px;
font-family: var(--theme-font); font-family: var(--theme-font);
......
import "./Desc.scss"; import styles from "./Desc.module.scss";
import { Fragment } from "react"; import { Fragment } from "react";
export const Desc: React.FC<{ desc?: string }> = ({ desc = "" }) => { export const Desc: React.FC<{ desc?: string }> = ({ desc = "" }) => {
if (!desc) return <></>; if (!desc) return <></>;
return ( return (
<div className="card-modal-desc"> <div className={styles.desc}>
{/* https://125naroom.com/web/2877 */}
{/* 牛逼的丸文字css教程 */}
<RegexWrapper <RegexWrapper
text={addSpaces(desc)} text={addSpaces(desc)}
re={/(①|②|③|④|⑤|⑥|⑦|⑧|⑨|⑩):.+?(?=((①|②|③|④|⑤|⑥|⑦|⑧|⑨|⑩):|$))/gs} re={/(①|②|③|④|⑤|⑥|⑦|⑧|⑨|⑩):.+?(?=((①|②|③|④|⑤|⑥|⑦|⑧|⑨|⑩):|$))/gs}
...@@ -43,7 +41,7 @@ const RegexWrapper: React.FC<{ ...@@ -43,7 +41,7 @@ const RegexWrapper: React.FC<{
const MaroListItem: React.FC<{ children: string }> = ({ children }) => { const MaroListItem: React.FC<{ children: string }> = ({ children }) => {
return ( return (
<div className="maro-item"> <div className={styles["maro-item"]}>
<span>{children[0]}</span> <span>{children[0]}</span>
<span> <span>
<RegexWrapper <RegexWrapper
...@@ -58,7 +56,7 @@ const MaroListItem: React.FC<{ children: string }> = ({ children }) => { ...@@ -58,7 +56,7 @@ const MaroListItem: React.FC<{ children: string }> = ({ children }) => {
const CircleListItem: React.FC<{ children: string }> = ({ children }) => { const CircleListItem: React.FC<{ children: string }> = ({ children }) => {
return children ? ( return children ? (
<div className="maro-item"> <div className={styles["maro-item"]}>
<span>{children[0]}</span> <span>{children[0]}</span>
<span>{children.slice(1)}</span> <span>{children.slice(1)}</span>
</div> </div>
......
.card-modal-root { .root {
.ant-drawer-content-wrapper { :global(.ant-drawer-content-wrapper) {
box-shadow: none; box-shadow: none;
} }
.card-modal-drawer { }
width: 90%;
left: 10%; .drawer {
--height: 640px; width: 90% !important;
top: calc((100% - var(--height)) / 2); left: 10%;
height: var(--height); --height: 640px;
position: relative; top: calc((100% - var(--height)) / 2);
border-radius: 6px; height: var(--height) !important;
background: #242424; position: relative;
.ant-drawer-header { border-radius: 6px;
padding: 15px 0; background: #242424;
.ant-drawer-header-title { :global(.ant-drawer-header) {
flex-direction: row-reverse; padding: 15px 0;
padding-left: 24px; :global(.ant-drawer-header-title) {
} flex-direction: row-reverse;
padding-left: 24px;
} }
} }
.card-modal-container {
position: relative;
height: 100%;
}
} }
.card-modal-name { .container {
font-weight: bold; position: relative;
font-size: 1.2rem; height: 100%;
} }
.atkLine { .atkLine {
...@@ -46,7 +43,7 @@ ...@@ -46,7 +43,7 @@
row-gap: 10px; row-gap: 10px;
} }
.card-modal-info { .info {
justify-content: space-between; justify-content: space-between;
position: relative; position: relative;
height: 204px; // TODO - fix this height: 204px; // TODO - fix this
......
import "./index.scss"; import styles from "./index.module.scss";
import { LeftOutlined } from "@ant-design/icons"; import { LeftOutlined } from "@ant-design/icons";
import { Divider, Drawer, Space, Tag } from "antd"; import { Divider, Drawer, Space, Tag } from "antd";
...@@ -57,14 +57,14 @@ export const CardModal = () => { ...@@ -57,14 +57,14 @@ export const CardModal = () => {
open={isOpen} open={isOpen}
placement="left" placement="left"
onClose={() => (store.isOpen = false)} onClose={() => (store.isOpen = false)}
rootClassName="card-modal-root" rootClassName={styles.root}
className="card-modal-drawer" className={styles.drawer}
mask={false} mask={false}
title={name} title={name}
closeIcon={<LeftOutlined />} closeIcon={<LeftOutlined />}
width={350} width={350}
> >
<div className="card-modal-container"> <div className={styles.container}>
<Space <Space
align="start" align="start"
size={18} size={18}
...@@ -75,7 +75,7 @@ export const CardModal = () => { ...@@ -75,7 +75,7 @@ export const CardModal = () => {
width={CARD_WIDTH} width={CARD_WIDTH}
style={{ borderRadius: 4 }} style={{ borderRadius: 4 }}
/> />
<Space direction="vertical" className="card-modal-info"> <Space direction="vertical" className={styles.info}>
<AtkLine atk={atk} def={def} /> <AtkLine atk={atk} def={def} />
<AttLine <AttLine
types={extraCardTypes(types || 0)} types={extraCardTypes(types || 0)}
...@@ -89,26 +89,6 @@ export const CardModal = () => { ...@@ -89,26 +89,6 @@ export const CardModal = () => {
</Space> </Space>
<Divider style={{ margin: "14px 0" }}></Divider> <Divider style={{ margin: "14px 0" }}></Divider>
<Desc desc={desc} /> <Desc desc={desc} />
{/* {nonEffectInteractivies.map((interactive, idx) => {
return (
<button
key={idx}
className="card-modal-btn"
onClick={() => {
sendSelectIdleCmdResponse(interactive.response);
cardModal.isOpen = false;
// 清空互动性
for (const card of cardStore.inner) {
card.idleInteractivities = [];
}
}}
>
{interactive.desc}
</button>
);
})}
<EffectButton meta={meta} effectInteractivies={effectInteractivies} /> */}
</div> </div>
</Drawer> </Drawer>
); );
...@@ -129,7 +109,7 @@ const AttLine = (props: { ...@@ -129,7 +109,7 @@ const AttLine = (props: {
.map((t) => fetchStrings("!system", Type2StringCodeMap.get(t) || 0)) .map((t) => fetchStrings("!system", Type2StringCodeMap.get(t) || 0))
.join("/"); .join("/");
return ( return (
<div className="attline"> <div className={styles.attline}>
{attribute && <Tag>{attribute}</Tag>} {attribute && <Tag>{attribute}</Tag>}
{race && <Tag>{race}</Tag>} {race && <Tag>{race}</Tag>}
{types && <Tag>{types}</Tag>} {types && <Tag>{types}</Tag>}
...@@ -138,14 +118,14 @@ const AttLine = (props: { ...@@ -138,14 +118,14 @@ const AttLine = (props: {
}; };
const AtkLine = (props: { atk?: number; def?: number }) => ( const AtkLine = (props: { atk?: number; def?: number }) => (
<Space size={10} className="atkLine" direction="vertical"> <Space size={10} className={styles.atkLine} direction="vertical">
<div> <div>
<div className="title">ATK</div> <div className={styles.title}>ATK</div>
<div className="number">{props.atk ?? "?"}</div> <div className={styles.number}>{props.atk ?? "?"}</div>
</div> </div>
<div> <div>
<div className="title">DEF</div> <div className={styles.title}>DEF</div>
<div className="number">{props.def ?? "?"}</div> <div className={styles.number}>{props.def ?? "?"}</div>
</div> </div>
</Space> </Space>
); );
...@@ -164,7 +144,7 @@ const _CounterLine = (props: { counters: { [type: number]: number } }) => { ...@@ -164,7 +144,7 @@ const _CounterLine = (props: { counters: { [type: number]: number } }) => {
return ( return (
<> <>
{counters.map((counter) => ( {counters.map((counter) => (
<div className="card-modal-counter">{counter}</div> <div>{counter}</div>
))} ))}
</> </>
); );
......
import "./index.scss"; import styles from "./index.module.scss";
import React, { CSSProperties } from "react"; import React, { CSSProperties } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
...@@ -63,14 +63,14 @@ export const EndModal: React.FC = () => { ...@@ -63,14 +63,14 @@ export const EndModal: React.FC = () => {
}} }}
onCancel={onReturn} onCancel={onReturn}
> >
<div className="end-container"> <div className={styles["end-container"]}>
<p <p
className="result" className={styles.result}
style={{ "--text-color": isWin ? "blue" : "red" } as CSSProperties} style={{ "--text-color": isWin ? "blue" : "red" } as CSSProperties}
> >
{isWin ? "Win" : "Defeated"} {isWin ? "Win" : "Defeated"}
</p> </p>
<p className="reason">{reason}</p> <p className={styles.reason}>{reason}</p>
{isReplay ? <></> : <p>{fetchStrings("!system", 1340)}</p>} {isReplay ? <></> : <p>{fetchStrings("!system", 1340)}</p>}
</div> </div>
</NeosModal> </NeosModal>
......
.neos-message .ant-message-notice-content { .message :global(.ant-message-notice-content) {
background-color: #333; background-color: #333;
} }
import "./index.scss"; import styles from "./index.module.scss";
import { message, notification } from "antd"; import { message, notification } from "antd";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
...@@ -103,7 +103,7 @@ export const showWaiting = (open: boolean) => { ...@@ -103,7 +103,7 @@ export const showWaiting = (open: boolean) => {
type: "loading", type: "loading",
content: fetchStrings("!system", 1390), content: fetchStrings("!system", 1390),
key: waitingKey, key: waitingKey,
className: "neos-message", className: styles["message"],
duration: 0, duration: 0,
}); });
clearTimeout(destoryTimer); clearTimeout(destoryTimer);
......
.neos-modal { .modal {
position: fixed; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
transition: 0.3s; transition: 0.3s;
} }
.neos-modal-mini { .mini {
top: 100% !important; top: 100% !important;
bottom: 0 !important; bottom: 0 !important;
transform: translateY(calc(50% - 66px)); transform: translateY(calc(50% - 66px));
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
} }
} }
.neos-modal-wrap { .wrap {
pointer-events: none; pointer-events: none;
} }
......
import "./index.scss"; import styles from "./index.module.scss";
import { MinusOutlined, UpOutlined } from "@ant-design/icons"; import { MinusOutlined, UpOutlined } from "@ant-design/icons";
import { Modal, type ModalProps } from "antd"; import { Modal, type ModalProps } from "antd";
...@@ -20,14 +20,14 @@ export const NeosModal: React.FC<Props> = (props) => { ...@@ -20,14 +20,14 @@ export const NeosModal: React.FC<Props> = (props) => {
return ( return (
<Modal <Modal
className={classNames("neos-modal", { "neos-modal-mini": mini })} className={classNames(styles.modal, { [styles["mini"]]: mini })}
centered centered
maskClosable={true} maskClosable={true}
onCancel={() => setMini(!mini)} onCancel={() => setMini(!mini)}
closeIcon={mini ? <UpOutlined /> : <MinusOutlined />} closeIcon={mini ? <UpOutlined /> : <MinusOutlined />}
bodyStyle={{ padding: "10px 0" }} bodyStyle={{ padding: "10px 0" }}
mask={!mini} mask={!mini}
wrapClassName={classNames({ "neos-modal-wrap": mini })} wrapClassName={classNames({ [styles.wrap]: mini })}
closable={canBeMinimized} closable={canBeMinimized}
{...props} {...props}
open={realOpen} open={realOpen}
......
import "./index.scss";
import { INTERNAL_Snapshot as Snapshot, proxy, useSnapshot } from "valtio"; import { INTERNAL_Snapshot as Snapshot, proxy, useSnapshot } from "valtio";
import { sendSelectMultiResponse, sendSelectSingleResponse } from "@/api"; import { sendSelectMultiResponse, sendSelectSingleResponse } from "@/api";
......
.checkcard-container { .container {
position: relative; position: relative;
// padding-left: 10px;
// &::after {
// position: absolute;
// width: 3px;
// height: 100%;
// content: "";
// z-index: 1;
// left: 0;
// top: 0;
// background-color: rgb(0, 54, 189);
// }
.btns { .btns {
width: 100%; width: 100%;
top: 50%; top: 50%;
...@@ -23,12 +12,12 @@ ...@@ -23,12 +12,12 @@
transform: translateY(-50%); transform: translateY(-50%);
} }
} }
.ant-pro-checkcard { :global(.ant-pro-checkcard) {
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
} }
// 多选卡片的样式 // 多选卡片的样式
.ant-pro-checkcard-checked { :global(.ant-pro-checkcard-checked) {
&::before { &::before {
position: absolute; position: absolute;
width: 100%; width: 100%;
...@@ -43,3 +32,25 @@ ...@@ -43,3 +32,25 @@
} }
} }
} }
.check-group {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
}
.check-card {
width: 100px;
aspect-ratio: var(--card-ratio);
margin-inline-end: 0;
margin-block-end: 0;
flex-shrink: 0;
}
.card {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
import "./index.scss";
import { CheckCard } from "@ant-design/pro-components"; import { CheckCard } from "@ant-design/pro-components";
import { Button, Card, Segmented, Space, Tooltip } from "antd"; import { Button, Card, Segmented, Space, Tooltip } from "antd";
import { CSSProperties, useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { INTERNAL_Snapshot as Snapshot, useSnapshot } from "valtio"; import { INTERNAL_Snapshot as Snapshot, useSnapshot } from "valtio";
import type { CardMeta, ygopro } from "@/api"; import type { CardMeta, ygopro } from "@/api";
...@@ -13,26 +11,7 @@ import { YgoCard } from "@/ui/Shared"; ...@@ -13,26 +11,7 @@ import { YgoCard } from "@/ui/Shared";
import { groupBy } from "../../utils"; import { groupBy } from "../../utils";
import { showCardModal } from "../CardModal"; import { showCardModal } from "../CardModal";
import { NeosModal } from "../NeosModal"; import { NeosModal } from "../NeosModal";
import styles from "./index.module.scss";
const YgoCardStyle = {
width: "100%",
height: "100%",
position: "absolute",
left: 0,
top: 0,
};
const CheckCardStyle = {
width: 100,
aspectRatio: 5.9 / 8.6,
marginInlineEnd: 0,
marginBlockEnd: 0,
flexShrink: 0,
};
const CheckGroupStyle = {
display: "grid",
gridTemplateColumns: "repeat(5, 1fr)",
gap: 10,
};
export interface SelectCardsModalProps { export interface SelectCardsModalProps {
isOpen: boolean; isOpen: boolean;
...@@ -148,88 +127,76 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({ ...@@ -148,88 +127,76 @@ export const SelectCardsModal: React.FC<SelectCardsModalProps> = ({
</> </>
} }
> >
<div className="check-container"> <Space direction="vertical" style={{ width: "100%", overflow: "hidden" }}>
<Space <Selector
direction="vertical" zoneOptions={zoneOptions}
style={{ width: "100%", overflow: "hidden" }} selectedZone={selectedZone}
> onChange={setSelectedZone as any}
<Selector />
zoneOptions={zoneOptions} {grouped.map(
selectedZone={selectedZone} (options, i) =>
onChange={setSelectedZone as any} options[0] === selectedZone && (
/> <div className={styles["container"]} key={i}>
{grouped.map( <CheckCard.Group
(options, i) => onChange={(res) => {
options[0] === selectedZone && ( setResult((isMultiple ? res : [res]) as any);
<div className="checkcard-container" key={i}> }}
<CheckCard.Group // TODO 考虑如何设置默认值,比如只有一个的,就直接选中
onChange={(res) => { multiple={isMultiple}
setResult((isMultiple ? res : [res]) as any); className={styles["check-group"]}
}} >
// TODO 考虑如何设置默认值,比如只有一个的,就直接选中 {options[1].map((card, j) => (
multiple={isMultiple} <Tooltip title={card.effectDesc} placement="bottom" key={j}>
style={CheckGroupStyle} {/* 这儿必须有一个div,不然tooltip不生效 */}
> <div>
{options[1].map((card, j) => ( <CheckCard
<Tooltip cover={
title={card.effectDesc} <YgoCard
placement="bottom" code={card.meta.id}
key={j} className={styles.card}
> />
{/* 这儿必须有一个div,不然tooltip不生效 */} }
<div> className={styles["check-card"]}
<CheckCard value={card}
cover={ onClick={() => {
<YgoCard showCardModal(card);
code={card.meta.id} }}
style={YgoCardStyle as CSSProperties} />
/> </div>
} </Tooltip>
style={CheckCardStyle} ))}
value={card} </CheckCard.Group>
onClick={() => { </div>
showCardModal(card); )
}} )}
/> <p>
</div> <span>
</Tooltip> {/* TODO: 这里的字体可以调整下 */}
))} {selecteds.length > 0 ? fetchStrings("!system", 212) : ""}
</CheckCard.Group> </span>
</div> </p>
) <div className={styles["check-group"]}>
)} {selecteds.map((card, i) => (
<p> <Tooltip
<span> title={card.effectDesc}
{/* TODO: 这里的字体可以调整下 */} placement="bottom"
{selecteds.length > 0 ? fetchStrings("!system", 212) : ""} key={grouped.length + i}
</span> >
</p> <div>
<div style={CheckGroupStyle}> <Card
{selecteds.map((card, i) => ( cover={
<Tooltip <YgoCard code={card.meta.id} className={styles.card} />
title={card.effectDesc} }
placement="bottom" className={styles["check-card"]}
key={grouped.length + i} onClick={() => {
> showCardModal(card);
<div> }}
<Card />
cover={ </div>
<YgoCard </Tooltip>
code={card.meta.id} ))}
style={YgoCardStyle as CSSProperties} </div>
/> </Space>
}
style={CheckCardStyle}
onClick={() => {
showCardModal(card);
}}
/>
</div>
</Tooltip>
))}
</div>
</Space>
</div>
</NeosModal> </NeosModal>
); );
}; };
......
.mat-bg {
display: flex;
flex-direction: column;
row-gap: var(--row-gap);
justify-content: center;
align-items: center;
background-color: transparent;
.row {
display: flex;
column-gap: var(--col-gap);
&.opponent {
flex-direction: row-reverse;
}
}
}
.block {
height: var(--block-height-m);
width: var(--block-width);
background: radial-gradient(#1d1d1d, #222);
position: relative;
&.extra {
margin-inline: calc(var(--block-width) / 2 + var(--col-gap) / 2);
}
&.szone {
height: var(--block-height-s);
}
&.highlight {
background: #102639;
cursor: pointer;
.triangle {
--color: #006eff;
transform: scale(1.5);
}
&:hover {
opacity: 0.7;
.triangle {
transform: scale(1.2);
}
}
}
.triangle {
width: 0;
height: 0;
--color: #333;
border-width: 4px;
border-style: solid;
position: absolute;
transition: 0.3s;
&:nth-of-type(1) {
border-color: var(--color) transparent transparent var(--color);
}
&:nth-of-type(2) {
border-color: var(--color) var(--color) transparent transparent;
right: 0;
}
&:nth-of-type(3) {
border-color: transparent var(--color) var(--color) transparent;
right: 0;
bottom: 0;
}
&:nth-of-type(4) {
border-color: transparent transparent var(--color) var(--color);
bottom: 0;
}
}
}
// 下面应该和moveToOutside、moveToGround对应
.other-blocks {
width: 0;
height: 0;
&.op {
transform: rotate(180deg);
}
position: absolute;
--height: var(--card-height-o);
--width: calc(var(--height) * var(--card-ratio));
--left: calc(
var(--col-gap) * 2 + var(--block-width) * 2.5 +
var(--block-outside-offset-x) + var(--width) / 2
);
--top: calc(
var(--row-gap) + var(--block-height-m) +
(var(--block-height-m) - var(--height)) / 2
);
.banish,
.field,
.graveyard,
.deck,
.extra-deck {
position: absolute;
transform: translate(-50%, -50%);
height: var(--height);
width: var(--width);
top: var(--top);
left: var(--left);
}
.field {
left: calc(-1 * var(--left));
}
.banish {
top: calc(var(--top) - var(--row-gap) - var(--height));
}
.deck {
--left: calc(
var(--deck-offset-x) + 2 * (var(--block-width) + var(--col-gap))
);
left: var(--left);
top: calc(
var(--deck-offset-y) + 2 * var(--block-height-m) + 2 * var(--row-gap)
);
transform: translate(-50%, -50%) rotate(calc(-1 * var(--deck-rotate-z)));
height: var(--deck-card-height);
width: calc(var(--deck-card-height) * var(--card-ratio));
&.extra-deck {
left: calc(-1 * var(--left));
transform: translate(-50%, -50%) rotate(var(--deck-rotate-z));
}
}
}
// 被禁用的样式
.disabled-cross {
width: 100%;
height: 100%;
cursor: not-allowed;
background: linear-gradient(
to top right,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0) calc(50% - 1.5px),
red 50%,
rgba(0, 0, 0, 0) calc(50% + 1.5px),
rgba(0, 0, 0, 0) 100%
),
linear-gradient(
to bottom right,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0) calc(50% - 1.5px),
red 50%,
rgba(0, 0, 0, 0) calc(50% + 1.5px),
rgba(0, 0, 0, 0) 100%
);
display: none;
}
.disabled-cross.show {
display: block;
}
.block.glowing {
--shadow-color: #13a1ff;
box-shadow: 0 0 3px 3px var(--shadow-color), 0 0 25px 2px #0099ff87;
background: var(--shadow-color);
border-radius: 2px;
.triangle {
display: none;
}
}
section#mat {
.mat-bg {
display: flex;
flex-direction: column;
row-gap: var(--row-gap);
justify-content: center;
align-items: center;
background-color: transparent;
.bg-row {
display: flex;
column-gap: var(--col-gap);
&.opponent {
flex-direction: row-reverse;
}
}
}
.block {
height: var(--block-height-m);
width: var(--block-width);
background: radial-gradient(#1d1d1d, #222);
position: relative;
&.extra {
margin-inline: calc(var(--block-width) / 2 + var(--col-gap) / 2);
}
&.szone {
height: var(--block-height-s);
}
&.highlight {
background: #102639;
cursor: pointer;
// animation: blink 1s linear infinite alternate;
.triangle {
--color: #006eff;
transform: scale(1.5);
}
&:hover {
opacity: 0.7;
.triangle {
transform: scale(1.2);
}
}
}
.triangle {
width: 0;
height: 0;
--color: #333;
border-width: 4px;
border-style: solid;
position: absolute;
transition: 0.3s;
&:nth-of-type(1) {
border-color: var(--color) transparent transparent var(--color);
}
&:nth-of-type(2) {
border-color: var(--color) var(--color) transparent transparent;
right: 0;
}
&:nth-of-type(3) {
border-color: transparent var(--color) var(--color) transparent;
right: 0;
bottom: 0;
}
&:nth-of-type(4) {
border-color: transparent transparent var(--color) var(--color);
bottom: 0;
}
}
}
// 下面应该和moveToOutside、moveToGround对应
.bg-other-blocks {
&.op {
transform: rotate(180deg);
}
position: absolute;
--height: var(--card-height-o);
--width: calc(var(--height) * var(--card-ratio));
--left: calc(
var(--col-gap) * 2 + var(--block-width) * 2.5 +
var(--block-outside-offset-x) + var(--width) / 2
);
--top: calc(
var(--row-gap) + var(--block-height-m) +
(var(--block-height-m) - var(--height)) / 2
);
.block {
position: absolute;
transform: translate(-50%, -50%);
height: var(--height);
width: var(--width);
top: var(--top);
left: var(--left);
}
.field {
left: calc(-1 * var(--left));
}
.banish {
top: calc(var(--top) - var(--row-gap) - var(--height));
}
.deck {
--left: calc(
var(--deck-offset-x) + 2 * (var(--block-width) + var(--col-gap))
);
left: var(--left);
top: calc(
var(--deck-offset-y) + 2 * var(--block-height-m) + 2 * var(--row-gap)
);
transform: translate(-50%, -50%) rotate(calc(-1 * var(--deck-rotate-z)));
height: var(--deck-card-height);
width: calc(var(--deck-card-height) * var(--card-ratio));
&.extra-deck {
left: calc(-1 * var(--left));
transform: translate(-50%, -50%) rotate(var(--deck-rotate-z));
}
}
}
}
// 被禁用的样式
.disabled-cross {
width: 100%;
height: 100%;
cursor: not-allowed;
background: linear-gradient(
to top right,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0) calc(50% - 1.5px),
red 50%,
rgba(0, 0, 0, 0) calc(50% + 1.5px),
rgba(0, 0, 0, 0) 100%
),
linear-gradient(
to bottom right,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0) calc(50% - 1.5px),
red 50%,
rgba(0, 0, 0, 0) calc(50% + 1.5px),
rgba(0, 0, 0, 0) 100%
);
display: none;
}
.disabled-cross.show {
display: block;
}
section#mat {
.block.glowing {
--card-shadow-color: #13a1ff;
box-shadow: 0 0 3px 3px var(--card-shadow-color), 0 0 25px 2px #0099ff87;
background: var(--card-shadow-color);
border-radius: 2px;
.triangle {
display: none;
}
}
}
import "./index.scss"; import styles from "./index.module.scss";
import classnames from "classnames"; import classnames from "classnames";
import { type INTERNAL_Snapshot as Snapshot, useSnapshot } from "valtio"; import { type INTERNAL_Snapshot as Snapshot, useSnapshot } from "valtio";
...@@ -27,9 +27,9 @@ const BgBlock: React.FC< ...@@ -27,9 +27,9 @@ const BgBlock: React.FC<
}) => ( }) => (
<div <div
{...rest} {...rest}
className={classnames("block", className, { className={classnames(styles.block, className, {
highlight, [styles.highlight]: highlight,
glowing, [styles.glowing]: glowing,
})} })}
> >
{<DecoTriangles />} {<DecoTriangles />}
...@@ -42,11 +42,11 @@ const BgExtraRow: React.FC<{ ...@@ -42,11 +42,11 @@ const BgExtraRow: React.FC<{
opSnap: Snapshot<BlockState[]>; opSnap: Snapshot<BlockState[]>;
}> = ({ meSnap, opSnap }) => { }> = ({ meSnap, opSnap }) => {
return ( return (
<div className={classnames("bg-row")}> <div className={classnames(styles.row)}>
{Array.from({ length: 2 }).map((_, i) => ( {Array.from({ length: 2 }).map((_, i) => (
<BgBlock <BgBlock
key={i} key={i}
className="extra" className={styles.extra}
onClick={() => { onClick={() => {
onBlockClick(meSnap[i].interactivity); onBlockClick(meSnap[i].interactivity);
onBlockClick(opSnap[i].interactivity); onBlockClick(opSnap[i].interactivity);
...@@ -64,11 +64,11 @@ const BgRow: React.FC<{ ...@@ -64,11 +64,11 @@ const BgRow: React.FC<{
opponent?: boolean; opponent?: boolean;
snap: Snapshot<BlockState[]>; snap: Snapshot<BlockState[]>;
}> = ({ szone = false, opponent = false, snap }) => ( }> = ({ szone = false, opponent = false, snap }) => (
<div className={classnames("bg-row", { opponent })}> <div className={classnames(styles.row, { [styles.opponent]: opponent })}>
{Array.from({ length: 5 }).map((_, i) => ( {Array.from({ length: 5 }).map((_, i) => (
<BgBlock <BgBlock
key={i} key={i}
className={classnames({ szone })} className={classnames({ [styles.szone]: szone })}
onClick={() => onBlockClick(snap[i].interactivity)} onClick={() => onBlockClick(snap[i].interactivity)}
disabled={snap[i].disabled} disabled={snap[i].disabled}
highlight={!!snap[i].interactivity} highlight={!!snap[i].interactivity}
...@@ -77,7 +77,7 @@ const BgRow: React.FC<{ ...@@ -77,7 +77,7 @@ const BgRow: React.FC<{
</div> </div>
); );
const BgOtherBlocks: React.FC<{ me?: boolean }> = ({ me }) => { const BgOtherBlocks: React.FC<{ op?: boolean }> = ({ op }) => {
useSnapshot(cardStore); useSnapshot(cardStore);
const meController = isMe(0) ? 0 : 1; const meController = isMe(0) ? 0 : 1;
const judgeGlowing = (zone: ygopro.CardZone) => const judgeGlowing = (zone: ygopro.CardZone) =>
...@@ -88,12 +88,15 @@ const BgOtherBlocks: React.FC<{ me?: boolean }> = ({ me }) => { ...@@ -88,12 +88,15 @@ const BgOtherBlocks: React.FC<{ me?: boolean }> = ({ me }) => {
const glowingGraveyard = judgeGlowing(ygopro.CardZone.GRAVE); const glowingGraveyard = judgeGlowing(ygopro.CardZone.GRAVE);
const glowingBanish = judgeGlowing(ygopro.CardZone.REMOVED); const glowingBanish = judgeGlowing(ygopro.CardZone.REMOVED);
return ( return (
<div className={classnames("bg-other-blocks", { me, op: !me })}> <div className={classnames(styles["other-blocks"], { [styles.op]: op })}>
<BgBlock className="banish" glowing={me && glowingBanish} /> <BgBlock className={styles.banish} glowing={!op && glowingBanish} />
<BgBlock className="graveyard" glowing={me && glowingGraveyard} /> <BgBlock className={styles.graveyard} glowing={!op && glowingGraveyard} />
<BgBlock className="field" /> <BgBlock className={styles.field} />
<BgBlock className="deck" /> <BgBlock className={styles.deck} />
<BgBlock className="deck extra-deck" glowing={me && glowingExtra} /> <BgBlock
className={classnames(styles.deck, styles["extra-deck"])}
glowing={!op && glowingExtra}
/>
</div> </div>
); );
}; };
...@@ -101,7 +104,7 @@ const BgOtherBlocks: React.FC<{ me?: boolean }> = ({ me }) => { ...@@ -101,7 +104,7 @@ const BgOtherBlocks: React.FC<{ me?: boolean }> = ({ me }) => {
export const Bg: React.FC = () => { export const Bg: React.FC = () => {
const snap = useSnapshot(placeStore.inner); const snap = useSnapshot(placeStore.inner);
return ( return (
<div className="mat-bg"> <div className={styles["mat-bg"]}>
<BgRow snap={snap[ygopro.CardZone.SZONE].op} szone opponent /> <BgRow snap={snap[ygopro.CardZone.SZONE].op} szone opponent />
<BgRow snap={snap[ygopro.CardZone.MZONE].op} opponent /> <BgRow snap={snap[ygopro.CardZone.MZONE].op} opponent />
<BgExtraRow <BgExtraRow
...@@ -110,8 +113,8 @@ export const Bg: React.FC = () => { ...@@ -110,8 +113,8 @@ export const Bg: React.FC = () => {
/> />
<BgRow snap={snap[ygopro.CardZone.MZONE].me} /> <BgRow snap={snap[ygopro.CardZone.MZONE].me} />
<BgRow snap={snap[ygopro.CardZone.SZONE].me} szone /> <BgRow snap={snap[ygopro.CardZone.SZONE].me} szone />
<BgOtherBlocks me />
<BgOtherBlocks /> <BgOtherBlocks />
<BgOtherBlocks op />
</div> </div>
); );
}; };
...@@ -127,11 +130,15 @@ const onBlockClick = (placeInteractivity: PlaceInteractivity) => { ...@@ -127,11 +130,15 @@ const onBlockClick = (placeInteractivity: PlaceInteractivity) => {
const DecoTriangles: React.FC = () => ( const DecoTriangles: React.FC = () => (
<> <>
{Array.from({ length: 4 }).map((_, i) => ( {Array.from({ length: 4 }).map((_, i) => (
<div className="triangle" key={i} /> <div className={styles.triangle} key={i} />
))} ))}
</> </>
); );
const DisabledCross: React.FC<{ disabled: boolean }> = ({ disabled }) => ( const DisabledCross: React.FC<{ disabled: boolean }> = ({ disabled }) => (
<div className={classnames("disabled-cross", { show: disabled })}></div> <div
className={classnames(styles["disabled-cross"], {
[styles.show]: disabled,
})}
></div>
); );
.mat-card {
position: absolute;
--card-height: 100px;
width: auto !important;
height: var(--card-height);
aspect-ratio: var(--card-ratio);
transform-style: preserve-3d;
.img-wrap {
transform-style: preserve-3d;
position: relative;
margin: auto auto;
top: 2%;
height: 96%;
width: 96%;
transform: translateZ(calc((var(--z) + var(--sub-z)) * 1px + 0.1px))
rotateY(calc(var(--ry) * 1deg));
transition: 0.2s scale;
cursor: pointer;
.cover,
.back {
width: 100% !important;
height: 100%;
position: absolute;
left: 0;
top: 0;
border-radius: 2px;
overflow: hidden;
}
.cover {
z-index: 1;
transform: translateZ(0.5px);
}
&.focusing .cover {
animation: focus 0.6s ease-in-out;
}
.back {
z-index: 0;
transform: translateZ(0px);
}
}
.shadow {
display: none;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: transparent;
// filter: blur(2px);
}
// 卡片被选中后的流光特效
// ref: https://github.com/Mr-majifu/Animated-Profile-Card02/blob/master/style.css
.streamer {
position: absolute;
inset: 0;
background: #000;
overflow: hidden;
}
.streamer::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 400%;
height: 80%;
background: linear-gradient(
transparent,
#45f3ff,
#45f3ff,
#45f3ff,
transparent
);
animation: stream 2s linear infinite;
}
.streamer::after {
content: "";
position: absolute;
/* https://developer.mozilla.org/en-US/docs/Web/CSS/inset */
inset: 3px;
background: #292929;
}
@keyframes stream {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.focus {
position: absolute;
width: calc(100% * var(--focus-scale));
height: calc(100% * var(--focus-scale));
left: calc((var(--focus-scale) - 1) * -50%);
top: calc((var(--focus-scale) - 1) * -50%);
outline: 3px solid #ddd;
filter: blur(calc(2px * 1.3 * var(--focus-scale)));
display: var(--focus-display);
opacity: var(--focus-opacity);
}
}
.mat-card.glowing .shadow {
--shadow-color: #0099ff;
display: block !important;
background: var(--shadow-color) !important;
border-radius: 5px;
box-shadow: 0 0 4px 0 var(--shadow-color), 0 0 25px 2px #0099ff87;
transform: translateZ(calc((var(--z)) * 1px + 0.1px));
}
@keyframes focus {
0% {
filter: brightness(1) contrast(1);
}
50% {
filter: brightness(1.5) contrast(1.1);
}
100% {
filter: brightness(1) contrast(1);
}
}
.dropdown {
:global(.ant-dropdown-menu) {
background-color: #333;
}
text-align: center;
}
.dropdown-disabled {
display: none;
}
section#mat {
.mat-card {
position: absolute;
--card-height: 100px;
width: auto !important;
height: var(--card-height);
aspect-ratio: var(--card-ratio);
transform-style: preserve-3d;
.card-img-wrap {
transform-style: preserve-3d;
position: relative;
margin: auto auto;
top: 2%;
height: 96%;
width: 96%;
transform: translateZ(calc((var(--z) + var(--sub-z)) * 1px + 0.1px))
rotateY(calc(var(--ry) * 1deg));
transition: 0.2s scale;
cursor: pointer;
&:hover {
}
.card-cover,
.card-back {
width: 100% !important;
height: 100%;
position: absolute;
left: 0;
top: 0;
border-radius: 2px;
overflow: hidden;
}
.card-cover {
z-index: 1;
transform: translateZ(0.5px);
}
&.focus .card-cover {
animation: focus 0.6s ease-in-out;
}
.card-back {
z-index: 0;
transform: translateZ(0px);
}
}
.card-shadow {
display: none;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: transparent;
// filter: blur(2px);
}
// 卡片被选中后的流光特效
// ref: https://github.com/Mr-majifu/Animated-Profile-Card02/blob/master/style.css
.card-streamer {
position: absolute;
inset: 0;
background: #000;
overflow: hidden;
}
.card-streamer::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 400%;
height: 80%;
background: linear-gradient(
transparent,
#45f3ff,
#45f3ff,
#45f3ff,
transparent
);
animation: stream 2s linear infinite;
}
.card-streamer::after {
content: "";
position: absolute;
/* https://developer.mozilla.org/en-US/docs/Web/CSS/inset */
inset: 3px;
background: #292929;
}
@keyframes stream {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.card-focus {
position: absolute;
width: calc(100% * var(--focus-scale));
height: calc(100% * var(--focus-scale));
left: calc((var(--focus-scale) - 1) * -50%);
top: calc((var(--focus-scale) - 1) * -50%);
outline: 3px solid #ddd;
filter: blur(calc(2px * 1.3 * var(--focus-scale)));
display: var(--focus-display);
opacity: var(--focus-opacity);
}
}
}
.mat-card.glowing .card-shadow {
--card-shadow-color: #0099ff;
display: block !important;
background: var(--card-shadow-color) !important;
border-radius: 5px;
box-shadow: 0 0 4px 0 var(--card-shadow-color), 0 0 25px 2px #0099ff87;
transform: translateZ(calc((var(--z)) * 1px + 0.1px));
}
@keyframes focus {
0% {
filter: brightness(1) contrast(1);
}
50% {
filter: brightness(1.5) contrast(1.1);
}
100% {
filter: brightness(1) contrast(1);
}
}
.card-dropdown {
.ant-dropdown-menu {
background-color: #333;
}
text-align: center;
}
.card-dropdown-disabled {
display: none;
}
import "./index.scss"; import styles from "./index.module.scss";
import { animated, to, useSpring } from "@react-spring/web"; import { animated, to, useSpring } from "@react-spring/web";
import { Dropdown, type MenuProps } from "antd"; import { Dropdown, type MenuProps } from "antd";
...@@ -39,7 +39,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => { ...@@ -39,7 +39,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
const card = cardStore.inner[idx]; const card = cardStore.inner[idx];
const snap = useSnapshot(card); const snap = useSnapshot(card);
const [styles, api] = useSpring<SpringApiProps>( const [spring, api] = useSpring<SpringApiProps>(
() => () =>
({ ({
x: 0, x: 0,
...@@ -273,47 +273,51 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => { ...@@ -273,47 +273,51 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
return ( return (
<animated.div <animated.div
className={classnames("mat-card", { glowing })} className={classnames(styles["mat-card"], { [styles.glowing]: glowing })}
style={ style={
{ {
transform: to( transform: to(
[styles.x, styles.y, styles.z, styles.rx, styles.ry, styles.rz], [spring.x, spring.y, spring.z, spring.rx, spring.ry, spring.rz],
(x, y, z, rx, ry, rz) => (x, y, z, rx, ry, rz) =>
`translate(${x}px, ${y}px) rotateX(${rx}deg) rotateZ(${rz}deg)` `translate(${x}px, ${y}px) rotateX(${rx}deg) rotateZ(${rz}deg)`
), ),
"--z": styles.z, "--z": spring.z,
"--sub-z": styles.subZ.to([0, 50, 100], [0, 200, 0]), // 中间高,两边低 "--sub-z": spring.subZ.to([0, 50, 100], [0, 200, 0]), // 中间高,两边低
"--ry": styles.ry, "--ry": spring.ry,
height: styles.height, height: spring.height,
zIndex: styles.zIndex, zIndex: spring.zIndex,
"--focus-scale": styles.focusScale, "--focus-scale": spring.focusScale,
"--focus-display": styles.focusDisplay, "--focus-display": spring.focusDisplay,
"--focus-opacity": styles.focusOpacity, "--focus-opacity": spring.focusOpacity,
opacity: styles.opacity, opacity: spring.opacity,
} as any as CSSProperties } as any as CSSProperties
} }
onClick={onClick} onClick={onClick}
> >
<div className="card-focus" /> <div className={styles.focus} />
<div className="card-shadow" /> <div className={styles.shadow} />
<Dropdown <Dropdown
menu={dropdownMenu} menu={dropdownMenu}
placement="top" placement="top"
overlayClassName={classnames("card-dropdown", { overlayClassName={classnames(styles.dropdown, {
"card-dropdown-disabled": dropdownMenuDisabled, [styles["dropdown-disabled"]]: dropdownMenuDisabled,
})} })}
arrow arrow
trigger={["click"]} trigger={["click"]}
> >
<div className={classnames("card-img-wrap", { focus: classFocus })}> <div
className={classnames(styles["img-wrap"], {
[styles.focusing]: classFocus,
})}
>
<YgoCard <YgoCard
className={classnames("card-cover")} className={styles.cover}
code={snap.code === 0 ? snap.meta.id : snap.code} code={snap.code === 0 ? snap.meta.id : snap.code}
/> />
<YgoCard className="card-back" isBack /> <YgoCard className={styles.back} isBack />
</div> </div>
</Dropdown> </Dropdown>
{snap.selected ? <div className="card-streamer" /> : <></>} {snap.selected ? <div className={styles.streamer} /> : <></>}
</animated.div> </animated.div>
); );
}); });
......
#life-bar-container { .container {
position: fixed; position: fixed;
display: flex; display: flex;
top: 0; top: 0;
......
import "./index.scss"; import styles from "./index.module.scss";
import { Progress } from "antd"; import { Progress } from "antd";
import classNames from "classnames"; import classNames from "classnames";
...@@ -56,7 +56,7 @@ export const LifeBar: React.FC = () => { ...@@ -56,7 +56,7 @@ export const LifeBar: React.FC = () => {
}, [currentPlayer]); }, [currentPlayer]);
return ( return (
<div id="life-bar-container"> <div className={styles.container}>
<LifeBarItem <LifeBarItem
active={!matStore.isMe(currentPlayer)} active={!matStore.isMe(currentPlayer)}
name={snapPlayer.getOpPlayer().name ?? "?"} name={snapPlayer.getOpPlayer().name ?? "?"}
...@@ -99,22 +99,24 @@ const LifeBarItem: React.FC<{ ...@@ -99,22 +99,24 @@ const LifeBarItem: React.FC<{
}} }}
> >
<div <div
className={classNames("life-bar", { className={classNames(styles["life-bar"], {
"life-bar-activated": active, "life-bar-activated": active,
})} })}
> >
<div className="name">{name}</div> <div className={styles.name}>{name}</div>
<div className="life">{<AnimatedNumbers animateToNumber={life} />}</div> <div className={styles.life}>
{<AnimatedNumbers animateToNumber={life} />}
</div>
</div> </div>
{active && ( {active && (
<div className="timer-container"> <div className={styles["timer-container"]}>
<Progress <Progress
type="circle" type="circle"
percent={Math.floor((timeLimit / 240) * 100)} percent={Math.floor((timeLimit / 240) * 100)}
strokeWidth={20} strokeWidth={20}
size={14} size={14}
/> />
<div className="timer-text">{timeText}</div> <div className={styles["timer-text"]}>{timeText}</div>
</div> </div>
)} )}
</div> </div>
......
section#mat { section.mat {
// margin-top: 200px;
// padding-top: 50px; // 先不管 后面调整
position: relative; position: relative;
#camera { width: "100%";
.camera {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -10,7 +9,7 @@ section#mat { ...@@ -10,7 +9,7 @@ section#mat {
align-items: center; align-items: center;
// perspective: var(--perspective); // perspective: var(--perspective);
} }
#plane { .plane {
transform: translateX(0) translateY(0) translateZ(0) transform: translateX(0) translateY(0) translateZ(0)
rotateX(var(--plane-rotate-x)); rotateX(var(--plane-rotate-x));
width: fit-content; width: fit-content;
...@@ -19,7 +18,7 @@ section#mat { ...@@ -19,7 +18,7 @@ section#mat {
} }
} }
.mat-card-container { .container {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
......
import "./index.scss"; import styles from "./index.module.scss";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
...@@ -9,32 +9,27 @@ import { Card } from "../Card"; ...@@ -9,32 +9,27 @@ import { Card } from "../Card";
// 后面再改名 // 后面再改名
export const Mat: React.FC = () => { export const Mat: React.FC = () => {
const snap = useSnapshot(cardStore.inner);
return ( return (
<section <section className={styles.mat}>
id="mat" <div className={styles.camera}>
style={{ <div className={styles.plane}>
width: "100%", <Bg />
}} <div className={styles.container}>
> <Cards />
<Plane> </div>
<Bg /> </div>
<CardContainer> </div>
{snap.map((_cardSnap, i) => (
<Card key={i} idx={i} />
))}
</CardContainer>
</Plane>
</section> </section>
); );
}; };
const Plane: React.FC<React.PropsWithChildren> = ({ children }) => ( const Cards: React.FC = () => {
<div id="camera"> const snap = useSnapshot(cardStore.inner);
<div id="plane">{children}</div> return (
</div> <>
); {snap.map((_cardSnap, i) => (
<Card key={i} idx={i} />
const CardContainer: React.FC<React.PropsWithChildren> = ({ children }) => ( ))}
<div className="mat-card-container">{children}</div> </>
); );
};
import "./index.scss"; import styles from "./index.module.scss";
import { import {
ArrowRightOutlined, ArrowRightOutlined,
...@@ -53,7 +53,12 @@ export const Menu = () => { ...@@ -53,7 +53,12 @@ export const Menu = () => {
: 7; : 7;
// PhaseType, 中文, response, 是否显示 // PhaseType, 中文, response, 是否显示
const phaseBind: [PhaseType, string, number, boolean][] = [ const phaseBind: [
phase: PhaseType,
label: string,
response: number,
show: boolean
][] = [
[PhaseType.DRAW, "抽卡阶段", -1, true], [PhaseType.DRAW, "抽卡阶段", -1, true],
[PhaseType.STANDBY, "准备阶段", -1, true], [PhaseType.STANDBY, "准备阶段", -1, true],
[PhaseType.MAIN1, "主要阶段 1", -1, true], [PhaseType.MAIN1, "主要阶段 1", -1, true],
...@@ -69,9 +74,9 @@ export const Menu = () => { ...@@ -69,9 +74,9 @@ export const Menu = () => {
const phaseSwitchItems: MenuProps["items"] = phaseBind const phaseSwitchItems: MenuProps["items"] = phaseBind
.filter(([, , , show]) => show) .filter(([, , , show]) => show)
.map(([phase, str, response], i) => ({ .map(([phase, label, response], key) => ({
key: i, key,
label: str, label,
disabled: currentPhase >= phase, disabled: currentPhase >= phase,
onClick: () => { onClick: () => {
if (response === 2) sendSelectIdleCmdResponse(response); if (response === 2) sendSelectIdleCmdResponse(response);
...@@ -95,37 +100,35 @@ export const Menu = () => { ...@@ -95,37 +100,35 @@ export const Menu = () => {
const globalDisable = !matStore.isMe(currentPlayer); const globalDisable = !matStore.isMe(currentPlayer);
return ( return (
<> <div className={styles["menu-container"]}>
<div className="menu-container"> <DropdownWithTitle
<DropdownWithTitle title="请选择要进入的阶段"
title="请选择要进入的阶段" menu={{ items: phaseSwitchItems }}
menu={{ items: phaseSwitchItems }} disabled={globalDisable}
>
<Button
icon={<StepForwardFilled style={{ transform: "scale(1.5)" }} />}
type="text"
disabled={globalDisable} disabled={globalDisable}
> >
<Button {phaseBind.find(([key]) => key === currentPhase)?.[1]}
icon={<StepForwardFilled style={{ transform: "scale(1.5)" }} />} </Button>
type="text" </DropdownWithTitle>
disabled={globalDisable} <Tooltip title="聊天室">
> <Button
{phaseBind.find(([key]) => key === currentPhase)?.[1]} icon={<MessageFilled />}
</Button> type="text"
</DropdownWithTitle>
<Tooltip title="聊天室">
<Button
icon={<MessageFilled />}
type="text"
disabled={globalDisable}
></Button>
</Tooltip>
<DropdownWithTitle
title="是否投降?"
menu={{ items: surrenderMenuItems }}
disabled={globalDisable} disabled={globalDisable}
> ></Button>
<Button icon={<CloseCircleFilled />} type="text"></Button> </Tooltip>
</DropdownWithTitle> <DropdownWithTitle
</div> title="是否投降?"
</> menu={{ items: surrenderMenuItems }}
disabled={globalDisable}
>
<Button icon={<CloseCircleFilled />} type="text"></Button>
</DropdownWithTitle>
</div>
); );
}; };
......
import "./index.scss"; import styles from "./index.module.scss";
import classNames from "classnames"; import classNames from "classnames";
import { CSSProperties, useMemo } from "react"; import { CSSProperties, useMemo } from "react";
...@@ -19,14 +19,14 @@ export const YgoCard: React.FC<Props> = (props) => { ...@@ -19,14 +19,14 @@ export const YgoCard: React.FC<Props> = (props) => {
className, className,
code = 0, code = 0,
isBack = false, isBack = false,
width = 80, width,
style, style,
onClick = () => {}, onClick = () => {},
} = props; } = props;
return useMemo( return useMemo(
() => ( () => (
<img <img
className={classNames("ygo-card", className)} className={classNames(styles["ygo-card"], className)}
src={getCardImgUrl(code, isBack)} src={getCardImgUrl(code, isBack)}
style={{ width, ...style }} style={{ width, ...style }}
onClick={onClick} onClick={onClick}
......
...@@ -4,6 +4,8 @@ import svgr from "vite-plugin-svgr"; ...@@ -4,6 +4,8 @@ import svgr from "vite-plugin-svgr";
import ydkLoader from "vite-ydk-loader"; import ydkLoader from "vite-ydk-loader";
import tsconfigPaths from "vite-tsconfig-paths"; import tsconfigPaths from "vite-tsconfig-paths";
import wasmPack from "vite-plugin-wasm-pack"; import wasmPack from "vite-plugin-wasm-pack";
import sassDts from "vite-plugin-sass-dts";
import path from "path";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
...@@ -15,9 +17,13 @@ export default defineConfig({ ...@@ -15,9 +17,13 @@ export default defineConfig({
svgr(), svgr(),
ydkLoader(), ydkLoader(),
tsconfigPaths(), tsconfigPaths(),
wasmPack("./rust-src") wasmPack("./rust-src"),
sassDts({
enabledMode: ["development"],
sourceDir: path.resolve(__dirname, "./src"),
}),
], ],
resolve: { resolve: {
extensions: [".js", ".json", ".ydk"], extensions: [".mjs", ".js", ".mts", ".ts", ".jsx", ".tsx", ".json", ".ydk"],
}, },
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment