|
|
@@ -1,369 +1,316 @@
|
|
|
<template>
|
|
|
- <LoginLayout >
|
|
|
-
|
|
|
- <!-- 背景 -->
|
|
|
- <template #background>
|
|
|
- <div class="login-bg"></div>
|
|
|
- <!-- 红绿灯动画 -->
|
|
|
- <div class="traffic-light-system">
|
|
|
- <!-- 主红绿灯(直行) -->
|
|
|
- <div class="traffic-light main-light">
|
|
|
- <div class="light red" :class="{ active: mainLight === 'red' }"></div>
|
|
|
- <div class="light yellow" :class="{ active: mainLight === 'yellow' }"></div>
|
|
|
- <div class="light green" :class="{ active: mainLight === 'green' }"></div>
|
|
|
- </div>
|
|
|
- <!-- 左拐灯 -->
|
|
|
- <div class="left-turn-light">
|
|
|
- <div class="arrow-light">
|
|
|
- <img
|
|
|
- v-if="leftLight === 'red'"
|
|
|
- :src="require('@/assets/icon_red_left.png')"
|
|
|
- alt="左拐红灯"
|
|
|
- />
|
|
|
- <img
|
|
|
- v-else-if="leftLight === 'yellow'"
|
|
|
- :src="require('@/assets/icon_yellow_left.png')"
|
|
|
- alt="左拐黄灯"
|
|
|
- />
|
|
|
- <img
|
|
|
- v-else
|
|
|
- :src="require('@/assets/icon_green_left.png')"
|
|
|
- alt="左拐绿灯"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- 人行道灯 -->
|
|
|
- <div class="pedestrian-light">
|
|
|
- <div class="person-light" :class="{ active: pedestrianLight === 'red' }">
|
|
|
- <img :src="require('@/assets/icon_red_person.png')" alt="红灯" class="person-img" />
|
|
|
- </div>
|
|
|
- <div class="person-light" :class="{ active: pedestrianLight === 'green' }">
|
|
|
- <img :src="require('@/assets/icon_green_person.png')" alt="绿灯" class="person-img" />
|
|
|
+ <div class="login-root">
|
|
|
+
|
|
|
+ <!-- 视频背景 -->
|
|
|
+ <video class="bg-video" autoplay muted loop playsinline @error="videoBgFailed = true">
|
|
|
+ <source src="@/assets/login/login-bg.mp4" type="video/mp4" />
|
|
|
+ </video>
|
|
|
+ <div v-if="videoBgFailed" class="bg-fallback"></div>
|
|
|
+
|
|
|
+ <!-- 顶部居中 KYLAND Logo -->
|
|
|
+ <header class="top-bar">
|
|
|
+ <img class="kyland-logo" src="@/assets/images/logo.png" alt="KYLAND" />
|
|
|
+ </header>
|
|
|
+
|
|
|
+ <!-- 主内容区:左右两栏 -->
|
|
|
+ <div class="main-content">
|
|
|
+
|
|
|
+ <!-- 左侧 -->
|
|
|
+ <div class="left-panel">
|
|
|
+ <div class="login-box">
|
|
|
+ <!-- 标题 -->
|
|
|
+ <div class="title-wrap">
|
|
|
+ <img class="title-img" src="@/assets/login/title.png" alt="交通信号控制平台—灵·智" />
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- Canvas 波浪动画 -->
|
|
|
- <div class="wave-canvas-container">
|
|
|
- <WaveCanvas />
|
|
|
- </div>
|
|
|
- </template>
|
|
|
-
|
|
|
- <template #main>
|
|
|
- <div class="page">
|
|
|
- <div class="content">
|
|
|
- <!-- 左侧酷炫区域 -->
|
|
|
- <div class="left">
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 右侧登录面板 -->
|
|
|
- <div class="right">
|
|
|
- <div class="login-container">
|
|
|
- <LoginForm></LoginForm>
|
|
|
+
|
|
|
+ <!-- 表单 -->
|
|
|
+ <div class="login-form">
|
|
|
+ <div class="field">
|
|
|
+ <img class="field-icon" src="@/assets/i_user.png" alt="" />
|
|
|
+ <span class="field-label">用户名</span>
|
|
|
+ <input class="inp" v-model.trim="username" placeholder="" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field">
|
|
|
+ <img class="field-icon" src="@/assets/i_lock.png" alt="" />
|
|
|
+ <span class="field-label">密码</span>
|
|
|
+ <input class="inp" type="password" v-model.trim="password" placeholder="" />
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
|
|
|
- <div class="copyright">
|
|
|
- <img class="copyright-logo" :src="require('@/assets/images/logo.png')" />
|
|
|
- <div>北京东土正创科技有限公司</div>
|
|
|
+ <div class="field-row" v-if="showCaptcha">
|
|
|
+ <div class="field cap-field">
|
|
|
+ <img class="field-icon" src="@/assets/i_captcha.png" alt="" />
|
|
|
+ <span class="field-label">验证码</span>
|
|
|
+ <input class="inp" v-model.trim="captchaInput" placeholder="" />
|
|
|
+ </div>
|
|
|
+ <CaptchaCanvas class="captcha-canvas" v-model="captchaCode" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="hint" v-if="hint">{{ hint }}</div>
|
|
|
+
|
|
|
+ <button class="btn-login" @click="onLogin" :disabled="loading">
|
|
|
+ {{ loading ? '登录中...' : '登录' }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </template>
|
|
|
|
|
|
- </LoginLayout>
|
|
|
+ <!-- 右侧留空(球体动画由视频背景实现) -->
|
|
|
+ <div class="right-panel"></div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 版权 -->
|
|
|
+ <footer class="copyright">北京东土正创科技有限公司</footer>
|
|
|
+
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import LoginLayout from "@/layouts/LoginLayout.vue";
|
|
|
-import LoginForm from "@/components/ui/LoginForm.vue";
|
|
|
-import WaveCanvas from "@/components/WaveCanvas.vue";
|
|
|
+import CaptchaCanvas from "@/components/CaptchaCanvas.vue";
|
|
|
+import { apiLogin } from "@/api";
|
|
|
|
|
|
export default {
|
|
|
name: "LoginPage",
|
|
|
- components: {
|
|
|
- LoginLayout,
|
|
|
- LoginForm,
|
|
|
- WaveCanvas
|
|
|
- },
|
|
|
+ components: { CaptchaCanvas },
|
|
|
created() {
|
|
|
- // 提前预加载 Cesium 瓦片
|
|
|
import('@/utils/cesiumPreloader').then(m => m.default.start());
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
- mainLight: 'green', // 直行灯: green, yellow, red
|
|
|
- leftLight: 'red', // 左拐灯: green, yellow, red
|
|
|
- pedestrianLight: 'green', // 人行道灯: green, red
|
|
|
- lightTimer: null,
|
|
|
- currentPhase: 0, // 0: 直行绿, 1: 黄灯, 2: 左拐绿
|
|
|
+ videoBgFailed: false,
|
|
|
+ username: "admin",
|
|
|
+ password: "123456",
|
|
|
+ captchaCode: "",
|
|
|
+ captchaInput: "",
|
|
|
+ hint: "",
|
|
|
+ loading: false,
|
|
|
+ loginFailedCount: Number(sessionStorage.getItem("loginFailedCount")) || 0,
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
|
-
|
|
|
- },
|
|
|
- mounted() {
|
|
|
- this.startTrafficLightCycle();
|
|
|
+ showCaptcha() { return this.loginFailedCount >= 3; },
|
|
|
},
|
|
|
- beforeDestroy() {
|
|
|
- if (this.lightTimer) {
|
|
|
- clearTimeout(this.lightTimer);
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
methods: {
|
|
|
- startTrafficLightCycle() {
|
|
|
- // 状态0: 直行绿灯10秒 + 人行道绿灯 + 左拐红灯
|
|
|
- this.currentPhase = 0;
|
|
|
- this.mainLight = 'green';
|
|
|
- this.leftLight = 'red';
|
|
|
- this.pedestrianLight = 'green';
|
|
|
-
|
|
|
- this.lightTimer = setTimeout(() => {
|
|
|
- this.switchToYellowPhase();
|
|
|
- }, 10000);
|
|
|
- },
|
|
|
-
|
|
|
- switchToYellowPhase() {
|
|
|
- // 状态1: 黄灯3秒 + 直行灭(实际变黄)+ 左拐红 + 人行道红
|
|
|
- this.currentPhase = 1;
|
|
|
- this.mainLight = 'yellow';
|
|
|
- this.leftLight = 'red';
|
|
|
- this.pedestrianLight = 'red';
|
|
|
-
|
|
|
- this.lightTimer = setTimeout(() => {
|
|
|
- this.switchToLeftTurnPhase();
|
|
|
- }, 3000);
|
|
|
+ async onLogin() {
|
|
|
+ this.hint = "";
|
|
|
+ if (this.showCaptcha) {
|
|
|
+ if (!this.captchaInput) { this.hint = "请输入验证码"; return; }
|
|
|
+ if (this.captchaInput.toLowerCase() !== this.captchaCode.toLowerCase()) {
|
|
|
+ this.hint = "验证码错误"; this.captchaInput = ""; return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.loading = true;
|
|
|
+ try {
|
|
|
+ const data = await apiLogin({ username: this.username, password: this.password, captcha: this.captchaInput });
|
|
|
+ this.loginFailedCount = 0;
|
|
|
+ sessionStorage.removeItem("loginFailedCount");
|
|
|
+ localStorage.setItem("token", data.token);
|
|
|
+ setTimeout(() => {
|
|
|
+ this.$router.push('/transition').catch(() => {});
|
|
|
+ }, 300);
|
|
|
+ } catch (e) {
|
|
|
+ this.hint = e.message || "登录失败";
|
|
|
+ this.loginFailedCount++;
|
|
|
+ sessionStorage.setItem("loginFailedCount", this.loginFailedCount);
|
|
|
+ this.captchaInput = "";
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
},
|
|
|
-
|
|
|
- switchToLeftTurnPhase() {
|
|
|
- // 状态2: 左拐绿灯10秒 + 直行红灯 + 人行道红灯
|
|
|
- this.currentPhase = 2;
|
|
|
- this.mainLight = 'red';
|
|
|
- this.leftLight = 'green';
|
|
|
- this.pedestrianLight = 'red';
|
|
|
-
|
|
|
- this.lightTimer = setTimeout(() => {
|
|
|
- this.switchToYellowPhase2();
|
|
|
- }, 10000);
|
|
|
- },
|
|
|
-
|
|
|
- switchToYellowPhase2() {
|
|
|
- // 状态3: 黄灯3秒 + 左拐黄灯 + 直行红灯 + 人行道红灯
|
|
|
- this.currentPhase = 3;
|
|
|
- this.mainLight = 'red';
|
|
|
- this.leftLight = 'yellow';
|
|
|
- this.pedestrianLight = 'red';
|
|
|
-
|
|
|
- this.lightTimer = setTimeout(() => {
|
|
|
- this.startTrafficLightCycle(); // 重新开始循环
|
|
|
- }, 3000);
|
|
|
- }
|
|
|
- }
|
|
|
+ },
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.login-bg {
|
|
|
- background: url('@/assets/images/login-background.png') no-repeat center/cover;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
-}
|
|
|
-.login-container {
|
|
|
- /* * 【自适应魔法】:计算当前屏幕与 1920 宽度的比例。
|
|
|
- * clamp(最小缩放比例, 动态计算比例, 最大缩放比例)
|
|
|
- * 当屏幕小于 1920 时,--s 会小于 1;当屏幕等于 1920 时,--s 为 1。
|
|
|
- */
|
|
|
- --s: clamp(0.5, 100vw / 1920, 1.2);
|
|
|
-
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: flex-end;
|
|
|
-
|
|
|
- /* 右侧边距也用 clamp 做动态响应:最小 20px,推荐 8vw,最大 150px */
|
|
|
- padding-right: clamp(20px, 8vw, 150px);
|
|
|
- box-sizing: border-box;
|
|
|
- pointer-events: auto;
|
|
|
-}
|
|
|
-
|
|
|
-.page{
|
|
|
- width: 100vw; height: 100vh;
|
|
|
+.login-root {
|
|
|
+ width: 100vw;
|
|
|
+ height: 100vh;
|
|
|
position: relative;
|
|
|
overflow: hidden;
|
|
|
- --s: 1;
|
|
|
- }
|
|
|
-
|
|
|
-.content{
|
|
|
- position:absolute;
|
|
|
- inset: 0;
|
|
|
- display:grid;
|
|
|
- grid-template-columns: 1fr 1fr;
|
|
|
- gap: clamp(calc(var(--s) * 14px), 1.8vw, calc(var(--s) * 28px));
|
|
|
- padding: clamp(calc(var(--s) * 14px), 2.4vw, calc(var(--s) * 30px));
|
|
|
- padding-top: calc(var(--s) * 60px); /* 给头部留空间 */
|
|
|
- align-items:center;
|
|
|
- z-index: 1;
|
|
|
-}
|
|
|
-
|
|
|
-/* 左侧 */
|
|
|
-.left{ min-width: 0; }
|
|
|
-
|
|
|
-/* 右侧面板 */
|
|
|
-.right{
|
|
|
- min-width: 0;
|
|
|
- display:flex;
|
|
|
- justify-content:center;
|
|
|
-}
|
|
|
-
|
|
|
-/* 版权信息 */
|
|
|
-.copyright {
|
|
|
+ background: #050a17;
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
flex-direction: column;
|
|
|
- font-size: 18px;
|
|
|
- color: #FFF;
|
|
|
- line-height: 30px;
|
|
|
- text-align: center;
|
|
|
- position: absolute;
|
|
|
- bottom: 20px;
|
|
|
- left: 0;
|
|
|
- justify-content: center;
|
|
|
- width: 100%;
|
|
|
-}
|
|
|
-
|
|
|
-.copyright-logo {
|
|
|
- width: 100px;
|
|
|
- margin-bottom: 10px;
|
|
|
}
|
|
|
|
|
|
-/* ================= 红绿灯系统 ================= */
|
|
|
-.traffic-light-system {
|
|
|
+.bg-video {
|
|
|
position: absolute;
|
|
|
- left: 30%;
|
|
|
- top: 25%;
|
|
|
- z-index: 10;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ min-width: 100%;
|
|
|
+ min-height: 100%;
|
|
|
+ width: auto;
|
|
|
+ height: auto;
|
|
|
+ object-fit: cover;
|
|
|
+ z-index: 0;
|
|
|
}
|
|
|
-
|
|
|
-/* 主红绿灯(直行) */
|
|
|
-.traffic-light {
|
|
|
+.bg-fallback {
|
|
|
position: absolute;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 12px;
|
|
|
- padding: 6px;
|
|
|
+ inset: 0;
|
|
|
+ background: url('@/assets/images/login-background.png') no-repeat center / cover;
|
|
|
+ z-index: 0;
|
|
|
}
|
|
|
|
|
|
-.main-light {
|
|
|
- left: 67px;
|
|
|
- top: 0px;
|
|
|
+.top-bar {
|
|
|
+ position: relative;
|
|
|
+ z-index: 2;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ height: 10vh;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
-
|
|
|
-.left-light {
|
|
|
- left: 0;
|
|
|
- top: 0;
|
|
|
+.kyland-logo {
|
|
|
+ height: clamp(30px, 5vh, 56px);
|
|
|
+ width: auto;
|
|
|
}
|
|
|
|
|
|
-/* 左拐箭头灯 */
|
|
|
-.left-turn-light {
|
|
|
- position: absolute;
|
|
|
- left: 4px;
|
|
|
- top: 4px;
|
|
|
+.main-content {
|
|
|
+ position: relative;
|
|
|
+ z-index: 2;
|
|
|
+ flex: 1;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 45fr 55fr;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 8vw;
|
|
|
+ min-height: 0;
|
|
|
}
|
|
|
|
|
|
-.arrow-light {
|
|
|
- width: 36px;
|
|
|
- height: 36px;
|
|
|
+.left-panel {
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
+ flex-direction: column;
|
|
|
justify-content: center;
|
|
|
- transition: all 0.3s ease;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
|
|
|
-.arrow-light img {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- object-fit: contain;
|
|
|
- filter: drop-shadow(none);
|
|
|
+.login-box {
|
|
|
+ width: 566px;
|
|
|
+ height: 353px;
|
|
|
+ background: transparent;
|
|
|
+ border: none;
|
|
|
+ box-shadow: none;
|
|
|
+ padding: 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
|
|
|
|
-.light {
|
|
|
- width: 30px;
|
|
|
- height: 30px;
|
|
|
- border-radius: 50%;
|
|
|
- opacity: 0.15;
|
|
|
- transition: all 0.3s ease;
|
|
|
- background: radial-gradient(circle at 30% 30%, rgba(100, 100, 100, 0.5), rgba(50, 50, 50, 0.3));
|
|
|
+.title-wrap {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 22px;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
-
|
|
|
-.light.red {
|
|
|
- background: radial-gradient(circle at 30% 30%, #ff4444, #cc0000);
|
|
|
+.title-img {
|
|
|
+ width: auto;
|
|
|
+ max-width: 100%;
|
|
|
}
|
|
|
|
|
|
-.light.red.active {
|
|
|
- opacity: 1;
|
|
|
- box-shadow: 0 0 15px #ff4444, 0 0 30px #ff4444, inset 0 0 10px rgba(255, 100, 100, 0.5);
|
|
|
+.login-form {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ flex: 1;
|
|
|
}
|
|
|
|
|
|
-.light.yellow {
|
|
|
- background: radial-gradient(circle at 30% 30%, #ffcc00, #ff9900);
|
|
|
+.field {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 60px;
|
|
|
+ width: 100%;
|
|
|
+ padding: 0 20px;
|
|
|
+ background: rgba(255, 255, 255, 0.08);
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.15);
|
|
|
+ border-radius: 16px;
|
|
|
+ margin-bottom: 36px;
|
|
|
+ transition: border-color 0.2s, background 0.2s;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.field:focus-within {
|
|
|
+ border-color: rgba(80, 180, 255, 0.5);
|
|
|
+ background: rgba(255, 255, 255, 0.12);
|
|
|
}
|
|
|
|
|
|
-.light.yellow.active {
|
|
|
- opacity: 1;
|
|
|
- box-shadow: 0 0 15px #ffcc00, 0 0 30px #ffcc00, inset 0 0 10px rgba(255, 200, 100, 0.5);
|
|
|
+.field-icon {
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ object-fit: contain;
|
|
|
+ opacity: 0.7;
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-right: 10px;
|
|
|
}
|
|
|
|
|
|
-.light.green {
|
|
|
- background: radial-gradient(circle at 30% 30%, #00ff44, #00cc33);
|
|
|
+.field-label {
|
|
|
+ color: rgba(180, 215, 255, 0.75);
|
|
|
+ font-size: 16px;
|
|
|
+ white-space: nowrap;
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-right: 12px;
|
|
|
}
|
|
|
|
|
|
-.light.green.active {
|
|
|
- opacity: 1;
|
|
|
- box-shadow: 0 0 15px #00ff44, 0 0 30px #00ff44, inset 0 0 10px rgba(100, 255, 150, 0.5);
|
|
|
+.inp {
|
|
|
+ flex: 1;
|
|
|
+ height: 100%;
|
|
|
+ border: none;
|
|
|
+ outline: none;
|
|
|
+ background: transparent;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 16px;
|
|
|
}
|
|
|
+.inp::placeholder { color: rgba(140, 180, 255, 0.3); }
|
|
|
|
|
|
-/* 人行道灯 */
|
|
|
-.pedestrian-light {
|
|
|
- position: absolute;
|
|
|
- left: 240px;
|
|
|
- top: 125px;
|
|
|
- width: 36px;
|
|
|
- height: 80px;
|
|
|
+.field-row {
|
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 8px;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 36px;
|
|
|
}
|
|
|
-
|
|
|
-.person-light {
|
|
|
- width: 36px;
|
|
|
- height: 36px;
|
|
|
- opacity: 0;
|
|
|
- transition: all 0.3s ease;
|
|
|
+.cap-field { flex: 1; margin-bottom: 0; }
|
|
|
+.captcha-canvas {
|
|
|
+ width: 120px;
|
|
|
+ height: 60px;
|
|
|
+ border-radius: 16px;
|
|
|
+ overflow: hidden;
|
|
|
+ border: 1px solid rgba(80, 160, 255, 0.35);
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
-.person-light.active {
|
|
|
- opacity: 1;
|
|
|
+.hint {
|
|
|
+ color: #ff4d4f;
|
|
|
+ font-size: 13px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ margin-top: -24px;
|
|
|
}
|
|
|
|
|
|
-.person-img {
|
|
|
+.btn-login {
|
|
|
width: 100%;
|
|
|
+ height: 64px;
|
|
|
+ background: linear-gradient(180deg, #3dd4f8 0%, #0fa8e8 50%, #0a90d8 100%);
|
|
|
+ border: none;
|
|
|
+ border-radius: 24px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 22px;
|
|
|
+ letter-spacing: 0.4em;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: filter 0.2s;
|
|
|
+ flex-shrink: 0;
|
|
|
+ box-shadow: 0 4px 20px rgba(10, 160, 230, 0.5);
|
|
|
+}
|
|
|
+.btn-login:hover { filter: brightness(1.1); }
|
|
|
+.btn-login:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
|
+
|
|
|
+.right-panel {
|
|
|
height: 100%;
|
|
|
- object-fit: contain;
|
|
|
}
|
|
|
|
|
|
-/* ================= Canvas 波浪容器 ================= */
|
|
|
-.wave-canvas-container {
|
|
|
- position: absolute;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- height: 400px;
|
|
|
- z-index: 5;
|
|
|
- pointer-events: none;
|
|
|
+.copyright {
|
|
|
+ position: relative;
|
|
|
+ z-index: 2;
|
|
|
+ text-align: center;
|
|
|
+ color: rgba(255, 255, 255, 0.85);
|
|
|
+ font-size: clamp(12px, 1vw, 18px);
|
|
|
+ padding-bottom: clamp(10px, 1.8vh, 22px);
|
|
|
+ flex-shrink: 0;
|
|
|
+ text-shadow: 0 0 8px rgba(0, 150, 255, 0.6), 0 1px 3px rgba(0, 0, 0, 0.8);
|
|
|
+ letter-spacing: 1px;
|
|
|
}
|
|
|
</style>
|