查看种子详细信息用户端和管理员端界面
Change-Id: I29e761d67a1eab741a91feb3f4c686055bb1b382
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 0000000..1766c19
--- /dev/null
+++ b/src/App.css
@@ -0,0 +1,2891 @@
+#root {
+ max-width: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* 覆盖默认样式,适配Mantine组件 */
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.mantine-Image-root {
+ border-radius: 8px;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+.mantine-Card-root {
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 0000000..b04728a
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
+import Home from './pages/Home';
+import AdminPage from './pages/AdminPage';
+import TorrentDetail from './components/torrentdetail';
+import TorrentDetailhelp from './components/torrentdetailhelp';
+import TorrentDetailcomplain from './components/torrentdetailcomplain';
+import TorrentList from './components/torrentlist';
+import UploadTorrent from './components/upload';
+import Navbar from './components/Navbar';
+import TorrentDetailmanage from './pages/managetorrentdetail';
+import RecommendAll from './components/RecommendAll';
+import UserAuth from './pages/UserAuth';
+import 'antd/dist/reset.css'; // Ant Design 默认样式
+import './App.css';
+import MainPage from './pages/MainPage';
+import Friend from './pages/Friend';
+import Community from './pages/Community';
+import UserCenter from './pages/UserCenter111';
+import UploadTorrentFull from './components/upload-full';
+import ShopPage from './pages/ShopPage';
+
+// 定义橙色主题(使用 Ant Design 的 orange-6 色值)
+const orangeTheme = {
+ token: {
+ colorPrimary: '#fa8c16', // orange-6
+ borderRadius: 4, // 可选:圆角大小
+ },
+};
+
+function App() {
+ return (
+ <div className="App">
+ {/* <div className="container mx-auto p-4"> */}
+ <Routes>
+ {/* <Route path="/" element={<Home />} /> */}
+ <Route path="/home" element={<MainPage />} />
+ <Route path="/admin" element={<AdminPage />} />
+ <Route path="/admin/:id" element={<TorrentDetailmanage />} />
+ <Route path="/process/:id" element={<TorrentDetailhelp />} />
+ <Route path="/complain-process/:id" element={<TorrentDetailcomplain />} />
+ <Route path="/torrents" element={<TorrentList />} />
+ <Route path="/upload" element={<UploadTorrent />} />
+ <Route path="/torrent/:id" element={<TorrentDetail />} />
+ <Route path="/recommend" element={<RecommendAll />} />
+ <Route path="/" element={<UserAuth />} />
+ <Route path="/Community" element={<Community />} />
+ <Route path="/friend" element={<Friend />} />
+ <Route path="/usercenter" element={<UserCenter />} />
+ <Route path="/uploadfull/:requestid" element={<UploadTorrentFull/>}/>
+ <Route path="/shop" element={<ShopPage />} />
+ </Routes>
+ {/* </div> */}
+ </div>
+ );
+}
+
+export default App;
\ No newline at end of file
diff --git a/src/Modal.css b/src/Modal.css
new file mode 100644
index 0000000..c37a106
--- /dev/null
+++ b/src/Modal.css
@@ -0,0 +1,118 @@
+/* 模态框遮罩层 */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5); /* 半透明黑色背景 */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000; /* 确保在最上层 */
+}
+
+/* 模态框内容 */
+.modal-content {
+ background-color: white;
+ border-radius: 12px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+ width: 90%;
+ max-width: 500px; /* 最大宽度 */
+ overflow: hidden;
+ animation: fadeInUp 0.3s ease-out; /* 淡入动画 */
+}
+
+/* 模态框头部 */
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ background-color: #e6f0ff; /* 浅蓝色背景 */
+ border-bottom: 1px solid #d0e3ff;
+}
+
+.modal-header h3 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ color: #0066cc; /* 蓝色标题 */
+}
+
+.close-btn {
+ background: none;
+ border: none;
+ font-size: 24px;
+ cursor: pointer;
+ color: #666;
+}
+
+.close-btn:hover {
+ color: #000;
+}
+
+/* 模态框主体 */
+.modal-body {
+ padding: 20px;
+}
+
+.modal-body p {
+ margin: 0;
+ font-size: 16px;
+ color: #333;
+}
+
+.highlight {
+ font-weight: 600;
+ color: #0066cc; /* 蓝色高亮 */
+}
+
+/* 模态框底部 */
+.modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ padding: 16px 20px;
+ background-color: #f5f5f5;
+}
+
+/* 按钮样式 */
+.btn-cancel {
+ padding: 8px 16px;
+ border: 1px solid #ccc;
+ background: white;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.btn-cancel:hover {
+ background: #f0f0f0;
+}
+
+.btn-confirm {
+ padding: 8px 16px;
+ background-color: #0066cc; /* 蓝色按钮 */
+ color: white;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.btn-confirm:hover {
+ background-color: #0052a3; /* 深蓝色 */
+}
+
+/* 动画 */
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
\ No newline at end of file
diff --git a/src/TorrentDetail.css b/src/TorrentDetail.css
new file mode 100644
index 0000000..d854a7c
--- /dev/null
+++ b/src/TorrentDetail.css
@@ -0,0 +1,48 @@
+.page-wrapper {
+ padding: 0px 24px 24px 24px;
+ background-color: #ffffff;
+ min-height: 100vh;
+}
+
+.custom-card {
+ background-color: #fafae7;
+ border: 1px solid #ffd591 !important;
+ border-radius: 12px !important;
+ box-shadow: 0 4px 12px rgba(255, 153, 0, 0.1);
+}
+
+.custom-table .ant-table-thead > tr > th {
+ background-color: #fff1b8 !important;
+ color: #a15c00;
+ font-weight: bold;
+}
+
+.info-card {
+ background-color: #fffefc;
+ border: 1px solid #ffe58f;
+ border-radius: 16px !important;
+ box-shadow: 0 6px 16px rgba(255, 140, 0, 0.15);
+ padding: 24px;
+ transition: box-shadow 0.3s ease;
+}
+
+.info-card:hover {
+ box-shadow: 0 10px 24px rgba(255, 140, 0, 0.25);
+}
+
+.info-title {
+ color: #d46b08;
+ font-weight: 700;
+ margin-bottom: 24px;
+}
+
+.custom-descriptions .ant-descriptions-item-label {
+ background-color: #fff7e6 !important;
+ color: #a15c00 !important;
+ font-size: 16px;
+}
+
+.custom-descriptions .ant-descriptions-item-content {
+ font-size: 15px;
+ color: #595959;
+}
diff --git a/src/__mocks__/axios.js b/src/__mocks__/axios.js
new file mode 100644
index 0000000..1e2caac
--- /dev/null
+++ b/src/__mocks__/axios.js
@@ -0,0 +1,4 @@
+// __mocks__/axios.js
+const axios = jest.createMockFromModule('axios');
+
+export default axios;
\ No newline at end of file
diff --git a/src/components/Torrentdetail.jsx b/src/components/Torrentdetail.jsx
new file mode 100644
index 0000000..a955b7b
--- /dev/null
+++ b/src/components/Torrentdetail.jsx
@@ -0,0 +1,206 @@
+import React, { useEffect, useState } from 'react';
+import { useParams, useNavigate } from 'react-router-dom'; // 添加 useNavigate
+import axios from 'axios';
+import {
+ Row,
+ Col,
+ Card,
+ Descriptions,
+ Table,
+ Typography,
+ Spin,
+ Tag,
+ Button, // 添加 Button 组件
+} from 'antd';
+import { Image as AntdImage } from 'antd';
+import '../TorrentDetail.css'; // 引入样式
+
+const { Title, Text } = Typography;
+
+const TorrentDetail = () => {
+ const { id } = useParams();
+ const navigate = useNavigate(); // 获取导航函数
+ const [torrent, setTorrent] = useState(null);
+ const [seeders, setSeeders] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [torrentLoading, setTorrentLoading] = useState(true);
+ const [seedersLoading, setSeedersLoading] = useState(false);
+
+ useEffect(() => {
+ const fetchTorrentDetails = async () => {
+ try {
+ setTorrentLoading(true);
+ const torrentRes = await axios.get(`http://localhost:8080/torrent/${id}`);
+ setTorrent(torrentRes.data);
+
+ setSeedersLoading(true);
+ const seedersRes = await axios.get(`http://localhost:8080/torrent/${torrentRes.data.infoHash}/seeders`);
+ setSeeders(seedersRes.data);
+ } catch (err) {
+ console.error('获取数据失败', err);
+ } finally {
+ setTorrentLoading(false);
+ setSeedersLoading(false);
+ setLoading(false);
+ }
+ };
+
+ fetchTorrentDetails();
+ }, [id]);
+
+ const formatSize = (bytes) => {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+ };
+
+ const formatSpeed = (bytesPerSec) => {
+ if (bytesPerSec < 1024) return bytesPerSec.toFixed(2) + ' B/s';
+ if (bytesPerSec < 1024 * 1024) return (bytesPerSec / 1024).toFixed(2) + ' KB/s';
+ return (bytesPerSec / (1024 * 1024)).toFixed(2) + ' MB/s';
+ };
+
+ const columns = [
+ {
+ title: '用户名',
+ dataIndex: 'username',
+ key: 'username',
+ align: 'center',
+ render: (text) => <Tag color="orange">{text}</Tag>,
+ },
+ {
+ title: '已上传',
+ dataIndex: 'uploaded',
+ key: 'uploaded',
+ align: 'center',
+ render: (text) => <Text>{formatSize(text)}</Text>,
+ },
+ {
+ title: '上传速度',
+ dataIndex: 'uploadSpeed',
+ key: 'uploadSpeed',
+ align: 'center',
+ render: (text) => <Text>{formatSpeed(text)}</Text>,
+ },
+ {
+ title: '已下载',
+ dataIndex: 'downloaded',
+ key: 'downloaded',
+ align: 'center',
+ render: (text) => <Text>{formatSize(text)}</Text>,
+ },
+ {
+ title: '下载速度',
+ dataIndex: 'downloadSpeed',
+ key: 'downloadSpeed',
+ align: 'center',
+ render: (text) => text > 0 ? <Text>{formatSpeed(text)}</Text> : <Text>-</Text>,
+ },
+ {
+ title: '客户端',
+ dataIndex: 'client',
+ key: 'client',
+ align: 'center',
+ },
+ {
+ title: '最后活动',
+ dataIndex: 'lastEvent',
+ key: 'lastEvent',
+ align: 'center',
+ },
+ ];
+
+ if (loading) return <div className="page-wrapper"><Spin size="large" /></div>;
+
+ return (
+ <div className="page-wrapper">
+ {/* 添加返回按钮 */}
+ <div className="mb-4">
+ <Button
+ type="primary"
+ onClick={() => navigate(-1)} // 返回上一页
+ style={{ marginBottom: '16px' }}
+ >
+ 返回列表
+ </Button>
+ </div>
+
+ <Row gutter={[16, 16]}>
+ <Col xs={24} md={8}>
+ <Card bordered={false} className="custom-card h-full">
+ {torrent.coverImagePath ? (
+ <AntdImage
+ src={torrent.coverImagePath}
+ alt="Torrent Cover"
+ className="w-full h-64 object-cover rounded"
+ placeholder={
+ <div className="w-full h-64 flex items-center justify-center bg-gray-100">
+ <Spin size="small" />
+ </div>
+ }
+ preview={false}
+ />
+ ) : (
+ <div className="w-full h-64 flex items-center justify-center bg-gray-100 rounded">
+ <Text type="secondary">无封面图片</Text>
+ </div>
+ )}
+ </Card>
+ </Col>
+
+ <Col xs={24} md={16}>
+ <Card className="info-card">
+ <Title level={1} className="info-title">
+ {torrent?.torrentTitle || '加载中...'}
+ </Title>
+
+ <Descriptions
+ bordered
+ column={{ xs: 1, sm: 2 }}
+ size="middle"
+ className="custom-descriptions"
+ labelStyle={{ fontWeight: 'bold', color: '#a15c00', fontSize: '16px' }}
+ contentStyle={{ fontSize: '15px' }}
+ >
+ <Descriptions.Item label="简介">{torrent.description || '暂无简介'}</Descriptions.Item>
+ <Descriptions.Item label="上传人">{torrent.uploader_id || '未知用户'}</Descriptions.Item>
+ <Descriptions.Item label="上传时间">{new Date(torrent.uploadTime).toLocaleString()}</Descriptions.Item>
+ <Descriptions.Item label="文件大小"><Text>{formatSize(torrent.torrentSize)}</Text></Descriptions.Item>
+ <Descriptions.Item label="下载数"><Text>{torrent.downloadCount || 0}</Text></Descriptions.Item>
+ <Descriptions.Item label="做种数"><Text>{seeders.length}</Text></Descriptions.Item>
+ <Descriptions.Item label="文件分辨率">{torrent.dpi || '未知'}</Descriptions.Item>
+ <Descriptions.Item label="文件字幕">{torrent.caption || '无'}</Descriptions.Item>
+ <Descriptions.Item label="最后做种时间">
+ {torrent.lastseed ? new Date(torrent.lastseed).toLocaleString() : '暂无'}
+ </Descriptions.Item>
+ </Descriptions>
+ </Card>
+ </Col>
+ </Row>
+
+ <Card className="custom-card mt-4">
+ <Title level={4} style={{ color: '#d46b08' }}>当前做种用户 ({seeders.length})</Title>
+ {seedersLoading ? (
+ <div className="p-4 text-center"><Spin size="small" /></div>
+ ) : seeders.length > 0 ? (
+ <Table
+ columns={columns}
+ dataSource={seeders}
+ rowKey={(record, index) => index}
+ pagination={false}
+ size="small"
+ className="custom-table"
+ />
+ ) : (
+ <div className="p-4 text-center text-gray-500 bg-gray-50 rounded">
+ 当前没有用户在做种
+ </div>
+ )}
+ </Card>
+ </div>
+ );
+};
+
+export default TorrentDetail;
\ No newline at end of file
diff --git a/src/main.jsx b/src/main.jsx
new file mode 100644
index 0000000..62c9250
--- /dev/null
+++ b/src/main.jsx
@@ -0,0 +1,11 @@
+// src/main.jsx 或 src/index.jsx
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import App from './App';
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+ <BrowserRouter>
+ <App />
+ </BrowserRouter>
+);
diff --git a/src/pages/managetorrentdetail.jsx b/src/pages/managetorrentdetail.jsx
new file mode 100644
index 0000000..029d06d
--- /dev/null
+++ b/src/pages/managetorrentdetail.jsx
@@ -0,0 +1,287 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import {
+ Descriptions,
+ Table,
+ Button,
+ Modal,
+ Image,
+ message,
+ Spin,
+ Input,
+ Select,
+ Pagination,
+ Space,
+ Card
+} from 'antd';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import axios from 'axios';
+import '../torrentdetailmanage.css'; // 引入样式
+
+const { confirm } = Modal;
+const { Option } = Select;
+
+//const { confirm } = Modal;
+
+const TorrentDetailmanage = () => {
+ const { id } = useParams(); // 从URL获取种子ID
+ const navigate = useNavigate(); // 用于返回上一页
+ const [torrent, setTorrent] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+
+ console.log('Torrent ID:', id);
+
+ const currentUserId = 1; // 示例,实际应从认证系统获取
+
+
+ // 格式化日期
+ const formatDate = (dateString) => {
+ if (!dateString) return '未知';
+ const date = new Date(dateString);
+ return date.toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ };
+
+ // 处理删除种子
+ const handleDeleteTorrent = async (torrentId) => {
+ if (!currentUserId) {
+ message.warning('请先登录');
+ return;
+ }
+
+ confirm({
+ title: '确认删除',
+ icon: <ExclamationCircleOutlined />,
+ content: '确定要删除这个种子吗?此操作不可恢复!',
+ onOk: async () => {
+ try {
+ await axios.delete(`http://localhost:8080/torrent/delete/${torrentId}`, {
+ params: { userid: currentUserId }
+ });
+ // 成功删除后,更新状态或返回上一页
+ setTorrent(null); // 清空当前种子详情
+ navigate(-1); // 返回上一页
+ message.success('种子删除成功');
+ } catch (err) {
+ console.error('删除种子失败', err);
+ if (err.response && err.response.status === 403) {
+ message.error('无权删除此种子');
+ } else {
+ message.error('删除种子失败');
+ }
+ }
+ }
+ });
+ };
+
+
+const handleDownloadTorrent = async (torrentId) => {
+ if (!currentUserId) {
+ message.warning('请先登录');
+ return;
+ }
+
+ setIsLoading(true); // 开始加载
+ try {
+ // 使用axios发送带有参数的GET请求
+ // const response = await axios.get(`http://localhost:8080/torrent/download/${torrentId}`, {
+ // params: { userId: currentUserId }, // 正确添加请求参数
+ // responseType: 'blob' // 重要:指定响应类型为blob以处理文件下载
+ // });
+
+ // // 创建下载链接
+ // const url = window.URL.createObjectURL(new Blob([response.data]));
+ // const link = document.createElement('a');
+ // link.href = url;
+ // //link.setAttribute('download', 'torrent_file.torrent'); // 可以设置为动态文件名
+ // document.body.appendChild(link);
+ // link.click();
+ // document.body.removeChild(link);
+ // window.URL.revokeObjectURL(url);
+ open(`http://localhost:8080/torrent/download/${torrentId}?userId=${currentUserId}`, '_blank');
+
+ message.success('种子下载开始');
+ } catch (err) {
+ console.error('下载种子失败', err);
+ if (err.response?.status === 404) {
+ message.error('种子不存在');
+ } else {
+ message.error('下载失败: ' + (err.response?.data?.message || err.message));
+ }
+ } finally {
+ setIsLoading(false); // 结束加载
+ }
+};
+
+
+ // 格式化文件大小
+ const formatFileSize = (bytes) => {
+ if (bytes === 0) return '0 Bytes';
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+ };
+
+ // 在组件函数内部添加这个函数
+const getPromotionName = (promotionId) => {
+ const promotionMap = {
+ 1: '上传加倍',
+ 2: '下载减半',
+ 3: '免费下载',
+ 0: '无促销'
+ };
+
+ return promotionMap[promotionId] || '未知促销';
+};
+
+ // 获取种子详情
+ useEffect(() => {
+ const fetchTorrentDetail = async () => {
+ try {
+ const response = await axios.get(`http://localhost:8080/torrent/${id}`);
+ if (response.status === 200) {
+ setTorrent(response.data);
+ } else {
+ setError('获取种子详情失败');
+ }
+ } catch (err) {
+ console.error('获取种子详情失败:', err);
+ if (err.response) {
+ if (err.response.status === 404) {
+ setError('种子不存在');
+ } else {
+ setError('获取种子详情失败: ' + err.response.data);
+ }
+ } else {
+ setError('网络错误,请稍后重试');
+ }
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchTorrentDetail();
+ }, [id]);
+
+ console.log('Torrent Detail:', torrent);
+
+ // 返回上一页
+ const handleBack = () => {
+ navigate(-1); // 返回上一页
+ };
+
+ // 如果正在加载
+ if (loading) {
+ return (
+ <div className="flex justify-center items-center h-96">
+ <Spin size="large" tip="加载中..." />
+ </div>
+ );
+ }
+
+ // 如果有错误
+ if (error) {
+ return (
+ <div className="p-6">
+ <Card>
+ <div className="text-center p-6">
+ <ExclamationCircleOutlined className="text-2xl text-red-500 mb-4" />
+ <h3 className="text-lg font-medium text-red-600 mb-2">错误</h3>
+ <p className="text-gray-600">{error}</p>
+ <Button type="primary" onClick={handleBack} className="mt-4">
+ 返回
+ </Button>
+ </div>
+ </Card>
+ </div>
+ );
+ }
+
+ // 如果种子不存在
+ if (!torrent) {
+ return (
+ <div className="p-6">
+ <Card>
+ <div className="text-center p-6">
+ <ExclamationCircleOutlined className="text-2xl text-yellow-500 mb-4" />
+ <h3 className="text-lg font-medium text-yellow-600 mb-2">种子不存在</h3>
+ <p className="text-gray-600">抱歉,您查找的种子不存在或已被删除。</p>
+ <Button type="primary" onClick={handleBack} className="mt-4">
+ 返回
+ </Button>
+ </div>
+ </Card>
+ </div>
+ );
+ }
+
+return (
+ <div className="torrent-detail-page">
+ <Card
+ className="torrent1-card"
+ title={<div className="torrent-title">种子详情</div>}
+ extra={
+ <Button type="primary" onClick={handleBack}>
+ 返回列表
+ </Button>
+ }
+ >
+ <Descriptions bordered column={1} size="middle">
+ <Descriptions.Item label="ID">{torrent.torrentid}</Descriptions.Item>
+
+ {torrent.coverImagePath && (
+ <Descriptions.Item label="封面图片">
+ <Image
+ className="torrent-cover-image"
+ src={torrent.coverImagePath}
+ alt="种子封面"
+ width={260}
+ height={160}
+ placeholder={
+ <div className="w-60 h-40 bg-gray-200 flex items-center justify-center">
+ 加载中...
+ </div>
+ }
+ />
+ </Descriptions.Item>
+ )}
+
+ <Descriptions.Item label="文件名">{torrent.filename}</Descriptions.Item>
+ <Descriptions.Item label="大小">{formatFileSize(torrent.torrentSize)}</Descriptions.Item>
+ <Descriptions.Item label="上传者ID">{torrent.uploader_id}</Descriptions.Item>
+ <Descriptions.Item label="上传时间">{formatDate(torrent.uploadTime)}</Descriptions.Item>
+ <Descriptions.Item label="下载次数">{torrent.downloadCount}</Descriptions.Item>
+ <Descriptions.Item label="促销">{getPromotionName(torrent.promotionid)}</Descriptions.Item>
+ <Descriptions.Item label="描述">{torrent.description || '无描述'}</Descriptions.Item>
+ </Descriptions>
+
+ <div className="torrent-buttons">
+ <Button
+ danger
+ onClick={() => handleDeleteTorrent(torrent.torrentid)}
+ loading={isLoading}
+ >
+ 删除
+ </Button>
+ <Button
+ type="primary"
+ onClick={() => handleDownloadTorrent(torrent.torrentid)}
+ loading={isLoading}
+ >
+ 下载
+ </Button>
+ </div>
+ </Card>
+ </div>
+);
+};
+
+export default TorrentDetailmanage;
\ No newline at end of file
diff --git a/src/test/TorrentDetail.test.jsx b/src/test/TorrentDetail.test.jsx
new file mode 100644
index 0000000..a540a81
--- /dev/null
+++ b/src/test/TorrentDetail.test.jsx
@@ -0,0 +1,91 @@
+import React from 'react';
+import { render, screen, waitFor } from '@testing-library/react';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import axios from 'axios';
+import TorrentDetail from '../components/Torrentdetail';
+import { vi } from 'vitest';
+
+
+beforeAll(() => {
+ Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: vi.fn().mockImplementation((query) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: vi.fn(),
+ removeListener: vi.fn(),
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn(),
+ })),
+ });
+});
+
+
+// 模拟 axios
+vi.mock('axios');
+
+describe('TorrentDetail Page', () => {
+ const mockTorrent = {
+ torrentTitle: '测试种子',
+ uploader_id: 'uploader123',
+ description: '这是一个测试种子描述',
+ uploadTime: '2024-06-01T12:00:00Z',
+ torrentSize: 104857600,
+ downloadCount: 10,
+ dpi: '1080p',
+ caption: '简体中文',
+ lastseed: '2024-06-05T14:00:00Z',
+ coverImagePath: 'http://example.com/cover.jpg',
+ infoHash: 'abc123'
+ };
+
+ const mockSeeders = [
+ {
+ username: 'Seeder1',
+ uploaded: 204857600,
+ uploadSpeed: 1048576,
+ downloaded: 102400,
+ downloadSpeed: 0,
+ client: 'qBittorrent',
+ lastEvent: '2024-06-06T11:22:00Z'
+ }
+ ];
+
+ beforeEach(() => {
+ axios.get.mockImplementation((url) => {
+ if (url.includes('/torrent/abc123/seeders')) {
+ return Promise.resolve({ data: mockSeeders });
+ }
+ if (url.includes('/torrent/123')) {
+ return Promise.resolve({ data: mockTorrent });
+ }
+ return Promise.reject(new Error('not found'));
+ });
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ test('renders torrent detail and seeders correctly', async () => {
+ render(
+ <MemoryRouter initialEntries={['/torrent/123']}>
+ <Routes>
+ <Route path="/torrent/:id" element={<TorrentDetail />} />
+ </Routes>
+ </MemoryRouter>
+ );
+
+ expect(document.querySelector('.ant-spin')).toBeInTheDocument();
+
+
+ await waitFor(() => {
+ expect(screen.getByText('测试种子')).toBeInTheDocument();
+ expect(screen.getByText('uploader123')).toBeInTheDocument();
+ expect(screen.getByText(/1080p/)).toBeInTheDocument();
+ expect(screen.getByText('Seeder1')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/test/setup.js b/src/test/setup.js
new file mode 100644
index 0000000..43c149b
--- /dev/null
+++ b/src/test/setup.js
@@ -0,0 +1,2 @@
+// src/test/setup.js
+import '@testing-library/jest-dom';
diff --git a/src/torrentdetailmanage.css b/src/torrentdetailmanage.css
new file mode 100644
index 0000000..b38cf6d
--- /dev/null
+++ b/src/torrentdetailmanage.css
@@ -0,0 +1,53 @@
+/* TorrentDetail.css */
+
+body, html, #root {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ background-color: #f4f6f9;
+ font-family: 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
+}
+
+.torrent-detail-page {
+ min-height: 100vh;
+ padding: 32px 48px;
+ background-color: #f4f6f9;
+ box-sizing: border-box;
+}
+
+.torrent1-card {
+ width: 100%;
+ max-width: 1080px;
+ min-width: 1000px; /* 确保不会太窄 */
+ margin: 0 auto;
+ border-radius: 16px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
+ border: none;
+ background: #fff;
+ padding: 32px;
+}
+
+.torrent-title {
+ font-size: 28px;
+ font-weight: 600;
+ color: #d46b08;
+ margin-bottom: 20px;
+}
+
+.torrent-cover-image {
+ border-radius: 12px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
+ border: 1px solid #eee;
+}
+
+.torrent-buttons {
+ margin-top: 32px;
+ display: flex;
+ justify-content: flex-end;
+ gap: 20px;
+}
+
+.torrent-buttons .ant-btn {
+ font-size: 16px;
+ padding: 10px 28px;
+}
diff --git a/src/torrentlist.css b/src/torrentlist.css
new file mode 100644
index 0000000..b6fa9d1
--- /dev/null
+++ b/src/torrentlist.css
@@ -0,0 +1,168 @@
+/* 加载状态 */
+.loading-container {
+ height: 16rem; /* 64 x 4 */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.spinner {
+ border: 4px solid #f3f3f3;
+ border-top: 4px solid #f59e0b; /* 橙黄色 */
+ border-radius: 50%;
+ width: 48px;
+ height: 48px;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+/* 容器和网格 */
+.torrents-container {
+ padding: 16px;
+ background-color: #f9fafb;
+ min-height: 100vh;
+}
+
+.torrents-grid {
+ display: grid;
+ /* grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); */
+ grid-template-columns: repeat(4, 1fr); /* 固定每行4列 */
+ gap: 24px;
+}
+
+.torrents1-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr); /* 固定每行4列 */
+ gap: 24px;
+}
+
+
+/* 卡片样式 */
+.torrent-card {
+ background-color: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgb(0 0 0 / 0.1);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ transition: box-shadow 0.3s ease;
+}
+
+.torrent-card:hover {
+ box-shadow: 0 6px 16px rgb(0 0 0 / 0.15);
+}
+
+/* 封面区域 */
+.cover {
+ height: 160px;
+ overflow: hidden;
+ background-color: #e5e7eb;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.cover-image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.no-cover {
+ color: #9ca3af;
+ font-size: 14px;
+}
+
+/* 信息区域 */
+.info {
+ padding: 16px;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #d97706; /* 橙色 */
+ margin: 0 0 6px 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.description {
+ font-size: 14px;
+ color: #6b7280;
+ margin: 0 0 12px 0;
+ height: 36px; /* 限制两行高度 */
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.details {
+ font-size: 12px;
+ color: #6b7280;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 16px;
+}
+
+.details span {
+ white-space: nowrap;
+}
+
+/* 操作按钮 */
+.actions {
+ margin-top: auto;
+ display: flex;
+ gap: 12px;
+}
+
+.btn {
+ flex: 1;
+ padding: 8px 12px;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ text-align: center;
+ text-decoration: none;
+ user-select: none;
+ transition: background-color 0.3s ease;
+ border: none;
+}
+
+.btn-download {
+ background-color: #f97316; /* 明亮橙色 */
+ color: white;
+}
+
+.btn-download:hover {
+ background-color: #ea580c; /* 深橙 */
+}
+
+.btn-detail {
+ background-color: #e5e7eb;
+ color: #374151;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.btn-detail:hover {
+ background-color: #d1d5db;
+}
+
+/* 没有数据提示 */
+.no-data {
+ text-align: center;
+ color: #6b7280;
+ font-size: 18px;
+ margin-top: 80px;
+}
+
diff --git a/src/upload.css b/src/upload.css
new file mode 100644
index 0000000..3e35e89
--- /dev/null
+++ b/src/upload.css
@@ -0,0 +1,151 @@
+
+
+ /* 表单组样式 */
+ .form-group {
+ margin-bottom: 1rem;
+ }
+
+ .form-label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+ }
+
+ .form-control {
+ width: 100%;
+ padding: 0.5rem;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ transition: border-color 0.3s;
+ }
+
+ .form-control:focus {
+ outline: none;
+ border-color: #ff8c00;
+ box-shadow: 0 0 0 3px rgba(255, 140, 0, 0.2);
+ }
+
+ /* 提交按钮样式 */
+ .submit-btn {
+ background-color: #ff8c00;
+ color: white;
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: 500;
+ transition: background-color 0.3s;
+ }
+
+ .submit-btn:hover {
+ background-color: #ff7f00;
+ }
+
+ /* 成功消息样式 */
+ .success-message {
+ margin-top: 1rem;
+ padding: 0.75rem;
+ background-color: #fff3cd;
+ color: #856404;
+ border: 1px solid #ffeeba;
+ border-radius: 4px;
+ }
+
+ /* 表单组样式 */
+ .form-group {
+ margin-bottom: 1.5rem;
+ }
+
+ .form-label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+ }
+
+ .form-control {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ transition: border-color 0.3s;
+ font-size: 1rem;
+ }
+
+ .form-control:focus {
+ outline: none;
+ border-color: #ff8c00;
+ box-shadow: 0 0 0 3px rgba(255, 140, 0, 0.2);
+ }
+
+ /* 上传区域样式 */
+ .upload-area {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s;
+ }
+
+ .upload-area:hover {
+ background-color: rgba(255, 140, 0, 0.05);
+ }
+
+ /* 自定义下拉框样式 */
+ .custom-select {
+ width: 100%;
+ padding: 0.75rem 2.5rem 0.75rem 1rem;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ appearance: none;
+ background-color: white;
+ font-size: 1rem;
+ cursor: pointer;
+ transition: border-color 0.3s;
+ }
+
+ .custom-select:focus {
+ outline: none;
+ border-color: #ff8c00;
+ box-shadow: 0 0 0 3px rgba(255, 140, 0, 0.2);
+ }
+
+ .select-arrow {
+ position: absolute;
+ right: 1rem;
+ top: 50%;
+ transform: translateY(-50%);
+ pointer-events: none;
+ }
+
+ /* 提交按钮样式 */
+ .submit-btn {
+ background-color: #ff8c00;
+ color: white;
+ padding: 0.75rem 2rem;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: 600;
+ font-size: 1rem;
+ transition: background-color 0.3s;
+ display: inline-block;
+ }
+
+ .submit-btn:hover {
+ background-color: #ff7f00;
+ }
+
+ /* 成功消息样式 */
+ .success-message {
+ margin-top: 1.5rem;
+ padding: 0.75rem 1.5rem;
+ background-color: #fff3cd;
+ color: #856404;
+ border: 1px solid #ffeeba;
+ border-radius: 8px;
+ }
+
+ /* 居中布局 */
+ .text-center {
+ text-align: center;
+ }
diff --git a/src/uploadtorrent.css b/src/uploadtorrent.css
new file mode 100644
index 0000000..213336c
--- /dev/null
+++ b/src/uploadtorrent.css
@@ -0,0 +1,95 @@
+/* UploadTorrent.css */
+.container {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10px;
+}
+
+.card {
+ width: 100%;
+ max-width: 1000px;;
+ min-width: 800px; /* 最小宽度 800px(确保不会太窄) */
+ margin: 0 auto;
+ padding: 20px;
+ background-color: #fff;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ border-radius: 8px;
+}
+
+.title {
+ text-align: center;
+ font-size: 1.5rem;
+ font-weight: bold;
+ color: #ff8c00;
+ margin-bottom: 20px;
+ padding-bottom: 10px;
+ border-bottom: 2px solid #ffe0b2;
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+}
+
+.uploadArea {
+ text-align: center;
+ padding: 20px;
+ border: 2px dashed #ffe0b2;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: border-color 0.3s;
+}
+
+.uploadArea:hover {
+ border-color: #ff8c00;
+}
+
+.uploadIcon {
+ color: #ffe0b2;
+ font-size: 2rem;
+}
+
+.uploadText {
+ margin-top: 10px;
+ font-size: 0.8rem;
+ color: #666;
+}
+
+.hiddenInput {
+ display: none;
+}
+
+.input {
+ width: 100%;
+ text-align: center;
+}
+
+.textArea {
+ width: 100%;
+ text-align: center;
+}
+
+.select {
+ width: 100%;
+}
+
+.submitButton {
+ width: 100%;
+ background-color: #ff8c00;
+ border-color: #ff8c00;
+}
+
+.submitButton:hover {
+ background-color: #ff7f00;
+ border-color: #ff7f00;
+}
+
+.successMessage {
+ text-align: center;
+ color: green;
+ font-weight: bold;
+ margin-top: 10px;
+}