feat(player): 重构 PC 端登录注册布局并优化注单栏与加载态
- 新增 DesktopAuthLayout 与 MouseRippleGrid,登录/注册/忘记密码采用管理端左右卡片布局(左侧大 Logo) - 登录页补齐 GoldSpinner 全表单加载遮罩与三语 logging_in 文案 - 注单栏与 Hub 侧栏使用 cebian.webp 背景,左侧联赛栏保持纯色
This commit is contained in:
BIN
apps/player/src/assets/images/cebian.webp
Normal file
BIN
apps/player/src/assets/images/cebian.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
284
apps/player/src/components/MouseRippleGrid.vue
Normal file
284
apps/player/src/components/MouseRippleGrid.vue
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* MouseRippleGrid — interactive grid that only appears near the cursor.
|
||||||
|
* Mouse movement creates expanding ripple waves that distort the grid.
|
||||||
|
*
|
||||||
|
* Performance notes:
|
||||||
|
* - Skips points outside the max effective radius of all ripples + mouse
|
||||||
|
* - Caps devicePixelRatio at 2
|
||||||
|
* - Respects prefers-reduced-motion (renders nothing)
|
||||||
|
* - pointer-events:none so form interactions are never blocked
|
||||||
|
*/
|
||||||
|
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||||
|
|
||||||
|
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
||||||
|
|
||||||
|
let ctx: CanvasRenderingContext2D | null = null;
|
||||||
|
let raf = 0;
|
||||||
|
let width = 0;
|
||||||
|
let height = 0;
|
||||||
|
let dpr = 1;
|
||||||
|
let parent: HTMLElement | null = null;
|
||||||
|
let resizeObs: ResizeObserver | null = null;
|
||||||
|
let reducedMotion = false;
|
||||||
|
|
||||||
|
/* ---- Mouse state ---- */
|
||||||
|
let mouseX = -9999;
|
||||||
|
let mouseY = -9999;
|
||||||
|
let lastRippleX = -9999;
|
||||||
|
let lastRippleY = -9999;
|
||||||
|
let mouseInside = false;
|
||||||
|
let mouseFade = 0; // smoothed 0→1 for graceful fade
|
||||||
|
|
||||||
|
/* ---- Ripple data ---- */
|
||||||
|
interface Ripple {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
born: number;
|
||||||
|
strength: number;
|
||||||
|
}
|
||||||
|
const ripples: Ripple[] = [];
|
||||||
|
|
||||||
|
/* ---- Tuning constants ---- */
|
||||||
|
const GRID = 34; // grid spacing px
|
||||||
|
const VISIBLE_R = 210; // mouse visibility radius
|
||||||
|
const RIPPLE_SPEED = 140; // px/s propagation
|
||||||
|
const RIPPLE_LIFE = 2000; // ms
|
||||||
|
const RIPPLE_SKIP_R = 380; // skip points farther than this from any ripple
|
||||||
|
const SIGMA = 42; // wave packet width
|
||||||
|
const MAX_DISPLACE = 14; // cap displacement px
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const c = canvasRef.value;
|
||||||
|
if (!c || !c.parentElement) return;
|
||||||
|
parent = c.parentElement;
|
||||||
|
const rect = parent.getBoundingClientRect();
|
||||||
|
dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||||||
|
width = rect.width;
|
||||||
|
height = rect.height;
|
||||||
|
c.width = Math.round(width * dpr);
|
||||||
|
c.height = Math.round(height * dpr);
|
||||||
|
c.style.width = width + 'px';
|
||||||
|
c.style.height = height + 'px';
|
||||||
|
ctx = c.getContext('2d');
|
||||||
|
if (ctx) ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMove(e: MouseEvent) {
|
||||||
|
if (!parent) return;
|
||||||
|
const rect = parent.getBoundingClientRect();
|
||||||
|
mouseX = e.clientX - rect.left;
|
||||||
|
mouseY = e.clientY - rect.top;
|
||||||
|
mouseInside = true;
|
||||||
|
|
||||||
|
const dx = mouseX - lastRippleX;
|
||||||
|
const dy = mouseY - lastRippleY;
|
||||||
|
if (dx * dx + dy * dy > 36) {
|
||||||
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
ripples.push({
|
||||||
|
x: mouseX,
|
||||||
|
y: mouseY,
|
||||||
|
born: performance.now(),
|
||||||
|
strength: Math.min(dist * 0.12, 5),
|
||||||
|
});
|
||||||
|
lastRippleX = mouseX;
|
||||||
|
lastRippleY = mouseY;
|
||||||
|
if (ripples.length > 30) ripples.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEnter() {
|
||||||
|
mouseInside = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLeave() {
|
||||||
|
mouseInside = false;
|
||||||
|
mouseX = -9999;
|
||||||
|
mouseY = -9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
function frame() {
|
||||||
|
raf = requestAnimationFrame(frame);
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
const now = performance.now();
|
||||||
|
|
||||||
|
/* Smooth fade in/out when mouse enters/leaves */
|
||||||
|
const target = mouseInside ? 1 : 0;
|
||||||
|
mouseFade += (target - mouseFade) * 0.05;
|
||||||
|
|
||||||
|
/* Purge expired ripples */
|
||||||
|
for (let i = ripples.length - 1; i >= 0; i--) {
|
||||||
|
if (now - ripples[i].born > RIPPLE_LIFE) ripples.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nothing to draw — clear and bail */
|
||||||
|
if (mouseFade < 0.004 && ripples.length === 0) {
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
const cols = Math.ceil(width / GRID) + 2;
|
||||||
|
const rows = Math.ceil(height / GRID) + 2;
|
||||||
|
|
||||||
|
/* Precompute active ripple data for this frame */
|
||||||
|
type ARipple = { x: number; y: number; age: number; strength: number };
|
||||||
|
const active: ARipple[] = [];
|
||||||
|
for (const r of ripples) {
|
||||||
|
const age = (now - r.born) / RIPPLE_LIFE;
|
||||||
|
if (age < 1) active.push({ x: r.x, y: r.y, age, strength: r.strength });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Pass 1: compute displaced positions + opacities ---- */
|
||||||
|
type Cell = { x: number; y: number; o: number };
|
||||||
|
const grid: Cell[][] = new Array(rows);
|
||||||
|
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
grid[row] = new Array(cols);
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const bx = (col - 0.5) * GRID;
|
||||||
|
const by = (row - 0.5) * GRID;
|
||||||
|
|
||||||
|
/* Base opacity from mouse proximity */
|
||||||
|
let opacity = 0;
|
||||||
|
if (mouseFade > 0.004) {
|
||||||
|
const mdx = bx - mouseX;
|
||||||
|
const mdy = by - mouseY;
|
||||||
|
const mdist = Math.sqrt(mdx * mdx + mdy * mdy);
|
||||||
|
if (mdist < VISIBLE_R) {
|
||||||
|
const f = 1 - mdist / VISIBLE_R;
|
||||||
|
opacity = f * f * 0.28 * mouseFade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ripple displacement */
|
||||||
|
let dx = 0;
|
||||||
|
let dy = 0;
|
||||||
|
let rippleO = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < active.length; i++) {
|
||||||
|
const r = active[i];
|
||||||
|
const rdx = bx - r.x;
|
||||||
|
const rdy = by - r.y;
|
||||||
|
const rdist = Math.sqrt(rdx * rdx + rdy * rdy);
|
||||||
|
if (rdist > RIPPLE_SKIP_R) continue;
|
||||||
|
|
||||||
|
const wavefront = r.age * RIPPLE_SPEED * (RIPPLE_LIFE / 1000);
|
||||||
|
const distFromFront = rdist - wavefront;
|
||||||
|
const env = Math.exp(-(distFromFront * distFromFront) / (2 * SIGMA * SIGMA));
|
||||||
|
const osc = Math.cos(distFromFront * 0.11);
|
||||||
|
const decay = (1 - r.age) * Math.exp(-rdist / 190);
|
||||||
|
const amp = r.strength * decay * env * osc * 6;
|
||||||
|
|
||||||
|
if (rdist > 0.5) {
|
||||||
|
dx += (rdx / rdist) * amp;
|
||||||
|
dy += (rdy / rdist) * amp;
|
||||||
|
}
|
||||||
|
rippleO = Math.max(rippleO, decay * env * 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cap displacement */
|
||||||
|
const dLen = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
if (dLen > MAX_DISPLACE) {
|
||||||
|
dx = (dx / dLen) * MAX_DISPLACE;
|
||||||
|
dy = (dy / dLen) * MAX_DISPLACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
opacity = Math.max(opacity, rippleO * mouseFade);
|
||||||
|
grid[row][col] = { x: bx + dx, y: by + dy, o: opacity };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Pass 2: draw grid lines ---- */
|
||||||
|
ctx.lineWidth = 0.6;
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const p = grid[row][col];
|
||||||
|
if (p.o < 0.008) continue;
|
||||||
|
|
||||||
|
/* Line to right neighbour */
|
||||||
|
if (col < cols - 1) {
|
||||||
|
const n = grid[row][col + 1];
|
||||||
|
if (n.o > 0.008) {
|
||||||
|
const lo = Math.min(p.o, n.o) * 0.55;
|
||||||
|
ctx.strokeStyle = `rgba(212,175,55,${lo})`;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(p.x, p.y);
|
||||||
|
ctx.lineTo(n.x, n.y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Line to bottom neighbour */
|
||||||
|
if (row < rows - 1) {
|
||||||
|
const n = grid[row + 1][col];
|
||||||
|
if (n.o > 0.008) {
|
||||||
|
const lo = Math.min(p.o, n.o) * 0.55;
|
||||||
|
ctx.strokeStyle = `rgba(212,175,55,${lo})`;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(p.x, p.y);
|
||||||
|
ctx.lineTo(n.x, n.y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Pass 3: draw intersection dots ---- */
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const p = grid[row][col];
|
||||||
|
if (p.o < 0.008) continue;
|
||||||
|
ctx.fillStyle = `rgba(240,216,117,${p.o})`;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(p.x, p.y, 1.3, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
resize();
|
||||||
|
|
||||||
|
const c = canvasRef.value;
|
||||||
|
if (!c || !c.parentElement) return;
|
||||||
|
parent = c.parentElement;
|
||||||
|
|
||||||
|
parent.addEventListener('mousemove', handleMove);
|
||||||
|
parent.addEventListener('mouseenter', handleEnter);
|
||||||
|
parent.addEventListener('mouseleave', handleLeave);
|
||||||
|
|
||||||
|
resizeObs = new ResizeObserver(resize);
|
||||||
|
resizeObs.observe(parent);
|
||||||
|
|
||||||
|
if (!reducedMotion) {
|
||||||
|
raf = requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
cancelAnimationFrame(raf);
|
||||||
|
if (parent) {
|
||||||
|
parent.removeEventListener('mousemove', handleMove);
|
||||||
|
parent.removeEventListener('mouseenter', handleEnter);
|
||||||
|
parent.removeEventListener('mouseleave', handleLeave);
|
||||||
|
}
|
||||||
|
resizeObs?.disconnect();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<canvas ref="canvasRef" class="ripple-grid" aria-hidden="true" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ripple-grid {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1181,7 +1181,7 @@ onUnmounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: rgba(16, 16, 16, 0.95);
|
background: var(--desktop-sidebar-panel-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-head {
|
.panel-head {
|
||||||
|
|||||||
199
apps/player/src/components/desktop/DesktopAuthLayout.vue
Normal file
199
apps/player/src/components/desktop/DesktopAuthLayout.vue
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* PC auth shell — admin-style card: large logo left, form slot right.
|
||||||
|
*/
|
||||||
|
import MouseRippleGrid from '../MouseRippleGrid.vue';
|
||||||
|
import LocaleSwitcher from '../LocaleSwitcher.vue';
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
/** Wider card for register / forgot-password forms */
|
||||||
|
wide?: boolean;
|
||||||
|
}>(),
|
||||||
|
{ wide: false },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="desktop-auth-page">
|
||||||
|
<div class="ambient-glow ambient-glow--1" aria-hidden="true"></div>
|
||||||
|
<div class="ambient-glow ambient-glow--2" aria-hidden="true"></div>
|
||||||
|
<MouseRippleGrid />
|
||||||
|
|
||||||
|
<div class="auth-wrap">
|
||||||
|
<div class="auth-card" :class="{ 'auth-card--wide': wide }">
|
||||||
|
<div class="form-lang">
|
||||||
|
<LocaleSwitcher compact />
|
||||||
|
</div>
|
||||||
|
<div class="form-body">
|
||||||
|
<aside class="form-brand">
|
||||||
|
<img src="/logo.png" alt="TheBet365" class="logo-large" />
|
||||||
|
</aside>
|
||||||
|
<div class="form-fields">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.desktop-auth-page {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100dvh;
|
||||||
|
background: linear-gradient(135deg, #0a0a0a 0%, #111 50%, #0d0d0d 100%);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-auth-page::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(ellipse 60% 60% at 50% 40%, rgba(212, 175, 55, 0.06) 0%, transparent 70%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ambient-glow {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(60px);
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.12;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ambient-glow--1 {
|
||||||
|
width: 320px;
|
||||||
|
height: 320px;
|
||||||
|
top: 10%;
|
||||||
|
left: 12%;
|
||||||
|
background: radial-gradient(circle, rgba(212, 175, 55, 0.5) 0%, transparent 70%);
|
||||||
|
animation: ambient-drift-1 12s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ambient-glow--2 {
|
||||||
|
width: 280px;
|
||||||
|
height: 280px;
|
||||||
|
bottom: 12%;
|
||||||
|
right: 12%;
|
||||||
|
background: radial-gradient(circle, rgba(240, 216, 117, 0.35) 0%, transparent 70%);
|
||||||
|
animation: ambient-drift-2 16s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ambient-drift-1 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0) scale(1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(30px, -20px) scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ambient-drift-2 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0) scale(1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-25px, 15px) scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-wrap {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100dvh;
|
||||||
|
padding: 24px 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 720px;
|
||||||
|
background: rgba(14, 14, 14, 0.92);
|
||||||
|
border: 1px solid rgba(212, 175, 55, 0.28);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.45);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-card--wide {
|
||||||
|
max-width: 820px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-lang {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 14px 18px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(200px, 260px) 1fr;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 28px 20px;
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
border-right: 1px solid rgba(212, 175, 55, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-large {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 220px;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
filter: drop-shadow(0 4px 24px rgba(212, 175, 55, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 22px 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.auth-wrap {
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-card,
|
||||||
|
.auth-card--wide {
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-body {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-brand {
|
||||||
|
padding: 20px 24px 12px;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid rgba(212, 175, 55, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-large {
|
||||||
|
max-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-fields {
|
||||||
|
padding: 18px 20px 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.ambient-glow--1,
|
||||||
|
.ambient-glow--2 {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -47,7 +47,7 @@ watch(
|
|||||||
width: var(--desktop-right-betslip-w);
|
width: var(--desktop-right-betslip-w);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
border-left: 1px solid var(--border);
|
border-left: 1px solid var(--border);
|
||||||
background: var(--bg-card);
|
background: var(--desktop-sidebar-panel-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ export default {
|
|||||||
},
|
},
|
||||||
auth:
|
auth:
|
||||||
{ login: 'Login',
|
{ login: 'Login',
|
||||||
|
logging_in: 'Logging in…',
|
||||||
register: 'Create Account',
|
register: 'Create Account',
|
||||||
logout: 'Log out',
|
logout: 'Log out',
|
||||||
username: 'Username',
|
username: 'Username',
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ export default {
|
|||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
login: 'Log Masuk',
|
login: 'Log Masuk',
|
||||||
|
logging_in: 'Sedang log masuk…',
|
||||||
register: 'Daftar Akaun',
|
register: 'Daftar Akaun',
|
||||||
logout: 'Log Keluar',
|
logout: 'Log Keluar',
|
||||||
username: 'Nama Pengguna',
|
username: 'Nama Pengguna',
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ export default {
|
|||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
login: '登录',
|
login: '登录',
|
||||||
|
logging_in: '登录中…',
|
||||||
register: '注册账号',
|
register: '注册账号',
|
||||||
logout: '退出登录',
|
logout: '退出登录',
|
||||||
username: '账号',
|
username: '账号',
|
||||||
|
|||||||
@@ -166,7 +166,7 @@
|
|||||||
|
|
||||||
.desktop-hub-left {
|
.desktop-hub-left {
|
||||||
border-right: 1px solid var(--border);
|
border-right: 1px solid var(--border);
|
||||||
background: rgba(14, 14, 14, 0.6);
|
background: var(--desktop-sidebar-panel-bg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
--desktop-bg: #050505;
|
--desktop-bg: #050505;
|
||||||
--desktop-border: #262626;
|
--desktop-border: #262626;
|
||||||
--desktop-sidebar-bg: rgba(20, 20, 20, 0.95);
|
--desktop-sidebar-bg: rgba(20, 20, 20, 0.95);
|
||||||
|
--desktop-sidebar-panel-bg: linear-gradient(rgba(16, 16, 16, 0.88), rgba(16, 16, 16, 0.88)), url('../../assets/images/cebian.webp') no-repeat center / cover;
|
||||||
|
|
||||||
/* Reset typography inside desktop */
|
/* Reset typography inside desktop */
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import LocaleSwitcher from '../components/LocaleSwitcher.vue';
|
|||||||
import PhoneCountrySelect from '../components/PhoneCountrySelect.vue';
|
import PhoneCountrySelect from '../components/PhoneCountrySelect.vue';
|
||||||
import loginBg from '../assets/images/h5bg.webp';
|
import loginBg from '../assets/images/h5bg.webp';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{ desktop?: boolean }>(), { desktop: false });
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -98,11 +100,11 @@ function goLogin() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="login-page" :style="{ backgroundImage: `url(${loginBg})` }">
|
<div class="login-page" :class="{ 'login-page--desktop': props.desktop }" :style="props.desktop ? undefined : { backgroundImage: `url(${loginBg})` }">
|
||||||
<div class="login-lang">
|
<div v-if="!props.desktop" class="login-lang">
|
||||||
<LocaleSwitcher compact />
|
<LocaleSwitcher compact />
|
||||||
</div>
|
</div>
|
||||||
<form @submit.prevent="submit" class="login-form ps-gold-frame">
|
<form @submit.prevent="submit" class="login-form" :class="{ 'ps-gold-frame': !props.desktop }">
|
||||||
<h2 class="form-title">{{ t('auth.forgot_password_title') }}</h2>
|
<h2 class="form-title">{{ t('auth.forgot_password_title') }}</h2>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@@ -203,6 +205,17 @@ function goLogin() {
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop {
|
||||||
|
min-height: auto;
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
@@ -212,6 +225,16 @@ function goLogin() {
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .login-form {
|
||||||
|
max-width: none;
|
||||||
|
width: 100%;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0;
|
||||||
|
border: none !important;
|
||||||
|
background: transparent !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.form-title {
|
.form-title {
|
||||||
margin: 0 0 2px;
|
margin: 0 0 2px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@@ -220,12 +243,24 @@ function goLogin() {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .form-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: left;
|
||||||
|
color: var(--primary-light, #f0d875);
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .field {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
@@ -233,6 +268,11 @@ label {
|
|||||||
letter-spacing: 0.03em;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop label {
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
.field-input {
|
.field-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
@@ -243,6 +283,7 @@ label {
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-input::placeholder {
|
.field-input::placeholder {
|
||||||
@@ -254,6 +295,22 @@ label {
|
|||||||
border-color: #555;
|
border-color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .field-input {
|
||||||
|
height: 46px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .field-input:focus {
|
||||||
|
border-color: var(--border-gold, #d4af37);
|
||||||
|
box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .field-input:hover:not(:focus) {
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
.inline-row {
|
.inline-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@@ -277,6 +334,19 @@ label {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease, background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-secondary {
|
||||||
|
height: 46px;
|
||||||
|
font-size: 13px;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-secondary:hover:not(:disabled) {
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
background: rgba(30, 30, 30, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:disabled {
|
.btn-secondary:disabled {
|
||||||
@@ -290,6 +360,20 @@ label {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
|
transition: background-color 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-login {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 15px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-login:hover:not(:disabled) {
|
||||||
|
background: rgba(212, 175, 55, 0.1);
|
||||||
|
border-color: var(--border-gold, #d4af37);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-login:disabled {
|
.btn-login:disabled {
|
||||||
@@ -306,6 +390,23 @@ label {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-skip {
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border, #333);
|
||||||
|
background: rgba(20, 20, 20, 0.6);
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.2s ease, color 0.2s ease, background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-skip:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
background: rgba(30, 30, 30, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip:active {
|
.btn-skip:active {
|
||||||
@@ -319,4 +420,12 @@ label {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .error {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(255, 69, 58, 0.08);
|
||||||
|
border-left: 2px solid var(--danger, #ff453a);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ import { useAppLocale } from '../composables/useAppLocale';
|
|||||||
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
|
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
|
||||||
import PhoneCountrySelect from '../components/PhoneCountrySelect.vue';
|
import PhoneCountrySelect from '../components/PhoneCountrySelect.vue';
|
||||||
import RobotVerify from '../components/RobotVerify.vue';
|
import RobotVerify from '../components/RobotVerify.vue';
|
||||||
|
import GoldSpinner from '../components/GoldSpinner.vue';
|
||||||
import loginBg from '../assets/images/h5bg.webp';
|
import loginBg from '../assets/images/h5bg.webp';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{ desktop?: boolean }>(), { desktop: false });
|
||||||
|
|
||||||
type LoginMode = 'account' | 'phone';
|
type LoginMode = 'account' | 'phone';
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
@@ -85,11 +88,20 @@ function goRegister() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="login-page" :style="{ backgroundImage: `url(${loginBg})` }">
|
<div class="login-page" :class="{ 'login-page--desktop': props.desktop }" :style="props.desktop ? undefined : { backgroundImage: `url(${loginBg})` }">
|
||||||
<div class="login-lang">
|
<div v-if="!props.desktop" class="login-lang">
|
||||||
<LocaleSwitcher compact />
|
<LocaleSwitcher compact />
|
||||||
</div>
|
</div>
|
||||||
<form @submit.prevent="submit" class="login-form ps-gold-frame">
|
<form
|
||||||
|
@submit.prevent="submit"
|
||||||
|
class="login-form"
|
||||||
|
:class="{ 'ps-gold-frame': !props.desktop, 'is-busy': loading }"
|
||||||
|
>
|
||||||
|
<div v-if="loading" class="form-busy-overlay" aria-hidden="true">
|
||||||
|
<GoldSpinner :size="40" :active="true" />
|
||||||
|
<p class="form-busy-text">{{ t('auth.logging_in') }}</p>
|
||||||
|
</div>
|
||||||
|
<h2 v-if="props.desktop" class="form-title">{{ t('auth.login') }}</h2>
|
||||||
<div v-if="loginMode === 'account'" class="field">
|
<div v-if="loginMode === 'account'" class="field">
|
||||||
<label>{{ t('auth.username') }}</label>
|
<label>{{ t('auth.username') }}</label>
|
||||||
<input
|
<input
|
||||||
@@ -98,6 +110,7 @@ function goRegister() {
|
|||||||
required
|
required
|
||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
maxlength="32"
|
maxlength="32"
|
||||||
|
:disabled="loading"
|
||||||
:placeholder="t('auth.username_placeholder')"
|
:placeholder="t('auth.username_placeholder')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,6 +125,7 @@ function goRegister() {
|
|||||||
type="tel"
|
type="tel"
|
||||||
required
|
required
|
||||||
autocomplete="tel-national"
|
autocomplete="tel-national"
|
||||||
|
:disabled="loading"
|
||||||
:placeholder="t('auth.phone_local_placeholder')"
|
:placeholder="t('auth.phone_local_placeholder')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,28 +133,43 @@ function goRegister() {
|
|||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>{{ t('auth.password') }}</label>
|
<label>{{ t('auth.password') }}</label>
|
||||||
<input v-model="password" class="field-input" type="password" required autocomplete="current-password" />
|
<input v-model="password" class="field-input" type="password" required autocomplete="current-password" :disabled="loading" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="btn-forgot" @click="goForgotPassword">
|
<button type="button" class="btn-forgot" :disabled="loading" @click="goForgotPassword">
|
||||||
{{ t('auth.forgot_password') }}
|
{{ t('auth.forgot_password') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button" class="btn-mode-switch" @click="switchLoginMode(loginMode === 'account' ? 'phone' : 'account')">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn-mode-switch"
|
||||||
|
:disabled="loading"
|
||||||
|
@click="switchLoginMode(loginMode === 'account' ? 'phone' : 'account')"
|
||||||
|
>
|
||||||
{{ loginMode === 'account' ? t('auth.login_by_phone') : t('auth.login_by_account') }}
|
{{ loginMode === 'account' ? t('auth.login_by_phone') : t('auth.login_by_account') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<RobotVerify ref="captchaRef" />
|
<RobotVerify ref="captchaRef" />
|
||||||
<p v-if="resetSuccess" class="success">{{ t('auth.reset_success') }}</p>
|
<p v-if="resetSuccess" class="success">{{ t('auth.reset_success') }}</p>
|
||||||
<p v-if="error" class="error">{{ error }}</p>
|
<p v-if="error" class="error">{{ error }}</p>
|
||||||
<button type="submit" class="btn-login btn-gold-outline" :disabled="loading">
|
<button
|
||||||
{{ t('auth.login') }}
|
type="submit"
|
||||||
|
class="btn-login btn-gold-outline"
|
||||||
|
:class="{ 'is-loading': loading }"
|
||||||
|
:disabled="loading"
|
||||||
|
:aria-busy="loading"
|
||||||
|
>
|
||||||
|
<span v-if="loading" class="btn-inline-loading">
|
||||||
|
<GoldSpinner :size="18" :active="true" />
|
||||||
|
{{ t('auth.logging_in') }}
|
||||||
|
</span>
|
||||||
|
<span v-else>{{ t('auth.login') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn-skip" @click="goRegister">
|
<button type="button" class="btn-skip" :disabled="loading" @click="goRegister">
|
||||||
{{ t('auth.go_register') }}
|
{{ t('auth.go_register') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<button type="button" class="btn-skip-light" @click="continueBrowsing">
|
<button v-if="!props.desktop" type="button" class="btn-skip-light" @click="continueBrowsing">
|
||||||
{{ t('auth.continue_browsing') }}
|
{{ t('auth.continue_browsing') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,13 +199,80 @@ function goRegister() {
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Desktop mode: fields only (card shell in DesktopAuthLayout) ===== */
|
||||||
|
.login-page--desktop {
|
||||||
|
min-height: auto;
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--primary-light, #f0d875);
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
transition: border-color 0.25s ease, box-shadow 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form.is-busy {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-busy-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 3;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: rgba(8, 8, 8, 0.72);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-busy-text {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(240, 216, 117, 0.95);
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-inline-loading {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .login-form {
|
||||||
|
max-width: none;
|
||||||
|
width: 100%;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0;
|
||||||
|
border: none !important;
|
||||||
|
background: transparent !important;
|
||||||
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
@@ -185,6 +281,10 @@ function goRegister() {
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .field {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.field-input {
|
.field-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
@@ -195,6 +295,7 @@ function goRegister() {
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-input:focus {
|
.field-input:focus {
|
||||||
@@ -202,6 +303,23 @@ function goRegister() {
|
|||||||
border-color: #555;
|
border-color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Desktop: visible focus ring per UX guideline */
|
||||||
|
.login-page--desktop .field-input {
|
||||||
|
height: 46px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .field-input:focus {
|
||||||
|
border-color: var(--border-gold, #D4AF37);
|
||||||
|
box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .field-input:hover:not(:focus) {
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
.inline-row {
|
.inline-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@@ -220,6 +338,11 @@ label {
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop label {
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-mode-switch {
|
.btn-mode-switch {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
margin: -2px 0 0;
|
margin: -2px 0 0;
|
||||||
@@ -230,6 +353,11 @@ label {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-mode-switch:hover {
|
||||||
|
color: rgba(240, 216, 117, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-forgot {
|
.btn-forgot {
|
||||||
@@ -242,12 +370,17 @@ label {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-forgot:active {
|
.btn-forgot:active {
|
||||||
color: rgba(255, 255, 255, 0.75);
|
color: rgba(255, 255, 255, 0.75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-forgot:hover {
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
.btn-mode-switch:active {
|
.btn-mode-switch:active {
|
||||||
color: rgba(240, 216, 117, 0.95);
|
color: rgba(240, 216, 117, 0.95);
|
||||||
}
|
}
|
||||||
@@ -259,6 +392,28 @@ label {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: background-color 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-login {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 15px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-login:hover:not(:disabled) {
|
||||||
|
background: rgba(212, 175, 55, 0.1);
|
||||||
|
border-color: var(--border-gold, #D4AF37);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-login:active:not(:disabled) {
|
||||||
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-login:disabled {
|
.btn-login:disabled {
|
||||||
@@ -266,6 +421,21 @@ label {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-login:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login.is-loading:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-forgot:disabled,
|
||||||
|
.btn-mode-switch:disabled,
|
||||||
|
.btn-skip:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-skip {
|
.btn-skip {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
padding: 8px 14px;
|
padding: 8px 14px;
|
||||||
@@ -275,12 +445,29 @@ label {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip:active {
|
.btn-skip:active {
|
||||||
color: rgba(255, 255, 255, 0.75);
|
color: rgba(255, 255, 255, 0.75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-skip {
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border, #333);
|
||||||
|
background: rgba(20, 20, 20, 0.6);
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.2s ease, color 0.2s ease, background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-skip:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
background: rgba(30, 30, 30, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.btn-skip-light {
|
.btn-skip-light {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: calc(20px + env(safe-area-inset-bottom));
|
bottom: calc(20px + env(safe-area-inset-bottom));
|
||||||
@@ -304,6 +491,15 @@ label {
|
|||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
animation: login-fade-in 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .error {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(255, 69, 58, 0.08);
|
||||||
|
border-left: 2px solid var(--danger, #FF453A);
|
||||||
}
|
}
|
||||||
|
|
||||||
.success {
|
.success {
|
||||||
@@ -312,5 +508,30 @@ label {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
animation: login-fade-in 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .success {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(110, 231, 160, 0.08);
|
||||||
|
border-left: 2px solid #6ee7a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes login-fade-in {
|
||||||
|
from { opacity: 0; transform: translateY(-4px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Respect reduced motion */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import PhoneCountrySelect from '../components/PhoneCountrySelect.vue';
|
|||||||
import GoldSpinner from '../components/GoldSpinner.vue';
|
import GoldSpinner from '../components/GoldSpinner.vue';
|
||||||
import loginBg from '../assets/images/h5bg.webp';
|
import loginBg from '../assets/images/h5bg.webp';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{ desktop?: boolean }>(), { desktop: false });
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const { initFromUser } = useAppLocale();
|
const { initFromUser } = useAppLocale();
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
@@ -101,11 +103,15 @@ const fieldError = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="login-page" :style="{ backgroundImage: `url(${loginBg})` }">
|
<div class="login-page" :class="{ 'login-page--desktop': props.desktop }" :style="props.desktop ? undefined : { backgroundImage: `url(${loginBg})` }">
|
||||||
<div class="login-lang">
|
<div v-if="!props.desktop" class="login-lang">
|
||||||
<LocaleSwitcher compact />
|
<LocaleSwitcher compact />
|
||||||
</div>
|
</div>
|
||||||
<form @submit.prevent="submit" class="login-form ps-gold-frame" :class="{ 'is-busy': loading }">
|
<form
|
||||||
|
@submit.prevent="submit"
|
||||||
|
class="login-form"
|
||||||
|
:class="{ 'ps-gold-frame': !props.desktop, 'is-busy': loading }"
|
||||||
|
>
|
||||||
<div v-if="loading" class="form-busy-overlay" aria-hidden="true">
|
<div v-if="loading" class="form-busy-overlay" aria-hidden="true">
|
||||||
<GoldSpinner :size="40" :active="true" />
|
<GoldSpinner :size="40" :active="true" />
|
||||||
<p class="form-busy-text">{{ t('auth.registering') }}</p>
|
<p class="form-busy-text">{{ t('auth.registering') }}</p>
|
||||||
@@ -203,7 +209,7 @@ const fieldError = () => {
|
|||||||
{{ t('auth.have_account') }}
|
{{ t('auth.have_account') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<button type="button" class="btn-skip-light" @click="continueBrowsing">
|
<button v-if="!props.desktop" type="button" class="btn-skip-light" @click="continueBrowsing">
|
||||||
{{ t('auth.continue_browsing') }}
|
{{ t('auth.continue_browsing') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -233,6 +239,18 @@ const fieldError = () => {
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Desktop mode: fields only (card shell in DesktopAuthLayout) ===== */
|
||||||
|
.login-page--desktop {
|
||||||
|
min-height: auto;
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -243,6 +261,16 @@ const fieldError = () => {
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .login-form {
|
||||||
|
max-width: none;
|
||||||
|
width: 100%;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0;
|
||||||
|
border: none !important;
|
||||||
|
background: transparent !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.login-form.is-busy {
|
.login-form.is-busy {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -285,12 +313,24 @@ const fieldError = () => {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .form-title {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
text-align: left;
|
||||||
|
color: var(--primary-light, #f0d875);
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .field {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.field-optional {
|
.field-optional {
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
@@ -302,12 +342,21 @@ label {
|
|||||||
letter-spacing: 0.03em;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop label {
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
.optional-tag {
|
.optional-tag {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: rgba(255, 255, 255, 0.4);
|
color: rgba(255, 255, 255, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .optional-tag {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.field-input {
|
.field-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
@@ -318,6 +367,7 @@ label {
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-input::placeholder {
|
.field-input::placeholder {
|
||||||
@@ -329,6 +379,23 @@ label {
|
|||||||
border-color: #555;
|
border-color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Desktop: visible focus ring per UX guideline */
|
||||||
|
.login-page--desktop .field-input {
|
||||||
|
height: 46px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .field-input:focus {
|
||||||
|
border-color: var(--border-gold, #D4AF37);
|
||||||
|
box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .field-input:hover:not(:focus) {
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
.inline-row {
|
.inline-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@@ -352,6 +419,19 @@ label {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease, background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-secondary {
|
||||||
|
height: 46px;
|
||||||
|
font-size: 13px;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-secondary:hover:not(:disabled) {
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
background: rgba(30, 30, 30, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:disabled {
|
.btn-secondary:disabled {
|
||||||
@@ -365,6 +445,28 @@ label {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: background-color 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-login {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 15px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-login:hover:not(:disabled) {
|
||||||
|
background: rgba(212, 175, 55, 0.1);
|
||||||
|
border-color: var(--border-gold, #D4AF37);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-login:active:not(:disabled) {
|
||||||
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-login:disabled {
|
.btn-login:disabled {
|
||||||
@@ -372,6 +474,10 @@ label {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-login:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-login.is-loading:disabled {
|
.btn-login.is-loading:disabled {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -385,12 +491,29 @@ label {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-skip:active {
|
.btn-skip:active {
|
||||||
color: rgba(255, 255, 255, 0.75);
|
color: rgba(255, 255, 255, 0.75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-skip {
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border, #333);
|
||||||
|
background: rgba(20, 20, 20, 0.6);
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.2s ease, color 0.2s ease, background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .btn-skip:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
background: rgba(30, 30, 30, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.btn-skip-light {
|
.btn-skip-light {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: calc(20px + env(safe-area-inset-bottom));
|
bottom: calc(20px + env(safe-area-inset-bottom));
|
||||||
@@ -416,5 +539,30 @@ label {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
|
animation: reg-fade-in 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page--desktop .error {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(255, 69, 58, 0.08);
|
||||||
|
border-left: 2px solid var(--danger, #FF453A);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes reg-fade-in {
|
||||||
|
from { opacity: 0; transform: translateY(-4px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Respect reduced motion */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,99 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
import DesktopAuthLayout from '../../components/desktop/DesktopAuthLayout.vue';
|
||||||
* Desktop Forgot Password View
|
|
||||||
*/
|
|
||||||
import ForgotPasswordView from '../ForgotPasswordView.vue';
|
import ForgotPasswordView from '../ForgotPasswordView.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="desktop-auth-shell">
|
<DesktopAuthLayout wide>
|
||||||
<div class="auth-brand">
|
<ForgotPasswordView desktop />
|
||||||
<div class="brand-inner">
|
</DesktopAuthLayout>
|
||||||
<img src="/logo.png" alt="TheBet365" class="brand-logo" />
|
|
||||||
<h1 class="brand-name">THEBET365</h1>
|
|
||||||
<p class="brand-tagline">重置您的登录密码</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="auth-card-wrap">
|
|
||||||
<div class="auth-card">
|
|
||||||
<ForgotPasswordView />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.desktop-auth-shell {
|
|
||||||
display: flex;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: var(--desktop-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-brand {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: linear-gradient(135deg, #0a0a0a 0%, #111 50%, #0d0d0d 100%);
|
|
||||||
border-right: 1px solid var(--desktop-border);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-brand::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background: radial-gradient(ellipse 60% 60% at 50% 40%, rgba(212,175,55,0.06) 0%, transparent 70%);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-inner {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-logo {
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
object-fit: contain;
|
|
||||||
filter: drop-shadow(0 4px 16px rgba(212,175,55,0.3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-name {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 900;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
color: var(--primary-light);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-tagline {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-card-wrap {
|
|
||||||
width: 480px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 40px;
|
|
||||||
background: var(--desktop-bg);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 380px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ onActivated(refreshUnreadCount);
|
|||||||
width: 220px;
|
width: 220px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-right: 1px solid var(--desktop-border);
|
border-right: 1px solid var(--desktop-border);
|
||||||
background: var(--desktop-sidebar-bg);
|
background: var(--desktop-sidebar-panel-bg);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,134 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
import DesktopAuthLayout from '../../components/desktop/DesktopAuthLayout.vue';
|
||||||
* Desktop Login View
|
|
||||||
* Reuses the existing LoginView form in a centered two-column auth layout.
|
|
||||||
*/
|
|
||||||
import LoginView from '../LoginView.vue';
|
import LoginView from '../LoginView.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="desktop-auth-shell">
|
<DesktopAuthLayout>
|
||||||
<div class="auth-brand">
|
<LoginView desktop />
|
||||||
<div class="brand-inner">
|
</DesktopAuthLayout>
|
||||||
<img src="/logo.png" alt="TheBet365" class="brand-logo" />
|
|
||||||
<h1 class="brand-name">THEBET365</h1>
|
|
||||||
<p class="brand-tagline">专业体育赛事投注平台</p>
|
|
||||||
<div class="brand-features">
|
|
||||||
<div class="feature-item">
|
|
||||||
<span class="feature-icon">⚡</span>
|
|
||||||
<span>实时赔率更新</span>
|
|
||||||
</div>
|
|
||||||
<div class="feature-item">
|
|
||||||
<span class="feature-icon">🔒</span>
|
|
||||||
<span>安全稳定平台</span>
|
|
||||||
</div>
|
|
||||||
<div class="feature-item">
|
|
||||||
<span class="feature-icon">💰</span>
|
|
||||||
<span>快速充值提款</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="auth-card-wrap">
|
|
||||||
<div class="auth-card">
|
|
||||||
<LoginView />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.desktop-auth-shell {
|
|
||||||
display: flex;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: var(--desktop-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-brand {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: linear-gradient(135deg, #0a0a0a 0%, #111 50%, #0d0d0d 100%);
|
|
||||||
border-right: 1px solid var(--desktop-border);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-brand::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background: radial-gradient(ellipse 60% 60% at 50% 40%, rgba(212,175,55,0.06) 0%, transparent 70%);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-inner {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-logo {
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
object-fit: contain;
|
|
||||||
filter: drop-shadow(0 4px 16px rgba(212,175,55,0.3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-name {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 900;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
color: var(--primary-light);
|
|
||||||
margin: 0;
|
|
||||||
text-shadow: 0 2px 12px rgba(212,175,55,0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-tagline {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-features {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-card-wrap {
|
|
||||||
width: 480px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 40px;
|
|
||||||
background: var(--desktop-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 380px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,100 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
import DesktopAuthLayout from '../../components/desktop/DesktopAuthLayout.vue';
|
||||||
* Desktop Register View
|
|
||||||
* Reuses the existing RegisterView form in a centered desktop auth layout.
|
|
||||||
*/
|
|
||||||
import RegisterView from '../RegisterView.vue';
|
import RegisterView from '../RegisterView.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="desktop-auth-shell">
|
<DesktopAuthLayout wide>
|
||||||
<div class="auth-brand">
|
<RegisterView desktop />
|
||||||
<div class="brand-inner">
|
</DesktopAuthLayout>
|
||||||
<img src="/logo.png" alt="TheBet365" class="brand-logo" />
|
|
||||||
<h1 class="brand-name">THEBET365</h1>
|
|
||||||
<p class="brand-tagline">加入我们,开始精彩体育投注</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="auth-card-wrap">
|
|
||||||
<div class="auth-card">
|
|
||||||
<RegisterView />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.desktop-auth-shell {
|
|
||||||
display: flex;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: var(--desktop-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-brand {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: linear-gradient(135deg, #0a0a0a 0%, #111 50%, #0d0d0d 100%);
|
|
||||||
border-right: 1px solid var(--desktop-border);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-brand::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background: radial-gradient(ellipse 60% 60% at 50% 40%, rgba(212,175,55,0.06) 0%, transparent 70%);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-inner {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-logo {
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
object-fit: contain;
|
|
||||||
filter: drop-shadow(0 4px 16px rgba(212,175,55,0.3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-name {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 900;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
color: var(--primary-light);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-tagline {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-card-wrap {
|
|
||||||
width: 520px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 40px;
|
|
||||||
background: var(--desktop-bg);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 420px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user