mirror of
https://github.com/stardrophere/InsightRadar.git
synced 2026-06-05 23:07:51 +08:00
界面优化
This commit is contained in:
@@ -5,7 +5,7 @@ from typing import Tuple
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
import random
|
||||||
from app.api.dependencies import get_db
|
from app.api.dependencies import get_db
|
||||||
from app.core.security import (
|
from app.core.security import (
|
||||||
create_access_token,
|
create_access_token,
|
||||||
@@ -52,10 +52,10 @@ def _normalize_email(email: str) -> str:
|
|||||||
def _build_verification_email(code: str, purpose_text: str, expire_minutes: int) -> str:
|
def _build_verification_email(code: str, purpose_text: str, expire_minutes: int) -> str:
|
||||||
return f"""
|
return f"""
|
||||||
<div style="font-family: Arial, sans-serif; line-height: 1.6; color: #222;">
|
<div style="font-family: Arial, sans-serif; line-height: 1.6; color: #222;">
|
||||||
<h2 style="margin-bottom: 12px;">InsightRadar Email Verification</h2>
|
<h2 style="margin-bottom: 12px;">InsightRadar 邮箱验证</h2>
|
||||||
<p>Your {purpose_text} verification code is:</p>
|
<p>您的{purpose_text}验证码是:</p>
|
||||||
<p style="font-size: 28px; font-weight: bold; letter-spacing: 4px; color: #0b57d0;">{code}</p>
|
<p style="font-size: 28px; font-weight: bold; letter-spacing: 4px; color: #0b57d0;">{code}</p>
|
||||||
<p>The code is valid for {expire_minutes} minutes. Do not share it with others.</p>
|
<p>该验证码在 {expire_minutes} 分钟内有效。请勿泄露给他人。</p>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -152,8 +152,8 @@ async def send_register_code(payload: RegisterCodeSendRequest, db: Session = Dep
|
|||||||
|
|
||||||
email_sent = await send_html_email(
|
email_sent = await send_html_email(
|
||||||
to_email=email,
|
to_email=email,
|
||||||
subject="InsightRadar Registration Code",
|
subject=f"【{code}】InsightRadar 注册验证码",
|
||||||
html_content=_build_verification_email(code, "registration", REGISTER_CODE_EXPIRE_MINUTES),
|
html_content=_build_verification_email(code, "注册", REGISTER_CODE_EXPIRE_MINUTES),
|
||||||
)
|
)
|
||||||
if not email_sent:
|
if not email_sent:
|
||||||
code_record.is_used = True
|
code_record.is_used = True
|
||||||
@@ -186,8 +186,8 @@ async def send_login_code(payload: LoginCodeSendRequest, db: Session = Depends(g
|
|||||||
|
|
||||||
email_sent = await send_html_email(
|
email_sent = await send_html_email(
|
||||||
to_email=email,
|
to_email=email,
|
||||||
subject="InsightRadar Login Code",
|
subject=f"【{code}】InsightRadar 登录验证码",
|
||||||
html_content=_build_verification_email(code, "login", LOGIN_CODE_EXPIRE_MINUTES),
|
html_content=_build_verification_email(code, "登录", LOGIN_CODE_EXPIRE_MINUTES),
|
||||||
)
|
)
|
||||||
if not email_sent:
|
if not email_sent:
|
||||||
code_record.is_used = True
|
code_record.is_used = True
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ async def send_html_email(
|
|||||||
to_email: str,
|
to_email: str,
|
||||||
subject: str,
|
subject: str,
|
||||||
html_content: str,
|
html_content: str,
|
||||||
sender_name: str = "AI 新闻",
|
sender_name: str = "AI 热点",
|
||||||
sender_email: str = None
|
sender_email: str = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""底层纯异步发送邮件工具"""
|
"""底层纯异步发送邮件工具"""
|
||||||
|
|||||||
@@ -817,7 +817,7 @@ watch(() => route.query.event, (newId) => {
|
|||||||
<section v-if="stats" class="widget-panel stats-widget">
|
<section v-if="stats" class="widget-panel stats-widget">
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<p class="stat-label">爬虫集群状态</p>
|
<p class="stat-label">爬虫状态</p>
|
||||||
<p class="stat-value">
|
<p class="stat-value">
|
||||||
<span class="status-dot-green"></span>
|
<span class="status-dot-green"></span>
|
||||||
{{ stats.active_sources }} 个源运行中
|
{{ stats.active_sources }} 个源运行中
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ const showPassword = ref(false)
|
|||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
const successMessage = ref('')
|
const successMessage = ref('')
|
||||||
const countdown = ref(0)
|
const countdown = ref(0)
|
||||||
|
const isSubmitting = ref(false)
|
||||||
|
const isSendingCode = ref(false)
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
@@ -83,6 +85,7 @@ async function handleSendLoginCode() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSendingCode.value = true
|
||||||
try {
|
try {
|
||||||
const result = await authStore.sendLoginVerificationCode(form.email.trim())
|
const result = await authStore.sendLoginVerificationCode(form.email.trim())
|
||||||
successMessage.value = result.message || '验证码已发送'
|
successMessage.value = result.message || '验证码已发送'
|
||||||
@@ -93,6 +96,8 @@ async function handleSendLoginCode() {
|
|||||||
startCooldown(retryAfter)
|
startCooldown(retryAfter)
|
||||||
}
|
}
|
||||||
errorMessage.value = error instanceof Error ? error.message : '验证码发送失败,请稍后重试'
|
errorMessage.value = error instanceof Error ? error.message : '验证码发送失败,请稍后重试'
|
||||||
|
} finally {
|
||||||
|
isSendingCode.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +111,7 @@ async function handleSubmit() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSubmitting.value = true
|
||||||
try {
|
try {
|
||||||
if (loginMode.value === 'password') {
|
if (loginMode.value === 'password') {
|
||||||
await authStore.loginWithPassword({
|
await authStore.loginWithPassword({
|
||||||
@@ -123,6 +129,8 @@ async function handleSubmit() {
|
|||||||
await router.replace(redirect)
|
await router.replace(redirect)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error instanceof Error ? error.message : '登录失败,请稍后重试'
|
errorMessage.value = error instanceof Error ? error.message : '登录失败,请稍后重试'
|
||||||
|
} finally {
|
||||||
|
isSubmitting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +154,30 @@ onUnmounted(() => {
|
|||||||
<p class="brand-desc">
|
<p class="brand-desc">
|
||||||
聚合多平台趋势,自动完成热点归并与摘要。你可以用密码登录,也可以直接使用邮箱验证码快速登录。
|
聚合多平台趋势,自动完成热点归并与摘要。你可以用密码登录,也可以直接使用邮箱验证码快速登录。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div class="feature-list">
|
||||||
|
<div class="feature-item">
|
||||||
|
<div class="feature-icon">🚀</div>
|
||||||
|
<div class="feature-text">
|
||||||
|
<strong>实时热搜追踪</strong>
|
||||||
|
<span>分钟级更新,覆盖微博、知乎、抖音等主流平台</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-item">
|
||||||
|
<div class="feature-icon">🤖</div>
|
||||||
|
<div class="feature-text">
|
||||||
|
<strong>AI 智能聚类</strong>
|
||||||
|
<span>自动识别同源事件,告别重复阅读的信息轰炸</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-item">
|
||||||
|
<div class="feature-icon">✨</div>
|
||||||
|
<div class="feature-text">
|
||||||
|
<strong>核心内容摘要</strong>
|
||||||
|
<span>一键获取事件全貌,省时省力掌握核心脉络</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ambient-glow"></div>
|
<div class="ambient-glow"></div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -204,9 +236,9 @@ onUnmounted(() => {
|
|||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
/>
|
/>
|
||||||
<button type="button" class="input-action-btn" @click="showPassword = !showPassword">
|
<!-- <button type="button" class="input-action-btn" @click="showPassword = !showPassword">
|
||||||
{{ showPassword ? '隐藏' : '显示' }}
|
{{ showPassword ? '隐藏' : '显示' }}
|
||||||
</button>
|
</button> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -222,8 +254,8 @@ onUnmounted(() => {
|
|||||||
placeholder="请输入 6 位验证码"
|
placeholder="请输入 6 位验证码"
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
/>
|
/>
|
||||||
<button type="button" class="input-action-btn" :disabled="!canSendCode" @click="handleSendLoginCode">
|
<button type="button" class="input-action-btn" :disabled="!canSendCode || isSendingCode" @click="handleSendLoginCode">
|
||||||
{{ countdown > 0 ? `${countdown}s` : '发送验证码' }}
|
{{ isSendingCode ? '发送中...' : (countdown > 0 ? `${countdown}s` : '发送验证码') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -251,7 +283,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn-primary" :disabled="authStore.loading" type="submit">
|
<button class="btn-primary" :disabled="authStore.loading" type="submit">
|
||||||
{{ authStore.loading ? '登录中...' : loginMode === 'password' ? '密码登录' : '邮箱验证码登录' }}
|
{{ isSubmitting ? '登录中...' : (loginMode === 'password' ? '密码登录' : '邮箱验证码登录') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -277,13 +309,38 @@ onUnmounted(() => {
|
|||||||
.brand-panel {
|
.brand-panel {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: none;
|
display: none;
|
||||||
background: linear-gradient(135deg, var(--bg-surface), var(--bg-base));
|
background: var(--bg-surface);
|
||||||
border-right: 1px solid var(--border-subtle);
|
border-right: 1px solid var(--border-subtle);
|
||||||
padding: 80px;
|
padding: 80px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 动态网格背景 */
|
||||||
|
.brand-panel::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
right: -50%;
|
||||||
|
bottom: -50%;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(128, 128, 128, 0.15) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(128, 128, 128, 0.15) 1px, transparent 1px);
|
||||||
|
background-size: 32px 32px;
|
||||||
|
background-position: center;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: grid-move 20s linear infinite;
|
||||||
|
mask-image: radial-gradient(circle at center, black 40%, transparent 80%);
|
||||||
|
-webkit-mask-image: radial-gradient(circle at center, black 40%, transparent 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes grid-move {
|
||||||
|
0% { transform: translate(0, 0); }
|
||||||
|
100% { transform: translate(32px, 32px); }
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 900px) {
|
@media (min-width: 900px) {
|
||||||
.brand-panel {
|
.brand-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -332,6 +389,60 @@ onUnmounted(() => {
|
|||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
transition: transform 0.3s ease, background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-text strong {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-text span {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ambient-glow {
|
.ambient-glow {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ const showConfirmPassword = ref(false)
|
|||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
const successMessage = ref('')
|
const successMessage = ref('')
|
||||||
const countdown = ref(0)
|
const countdown = ref(0)
|
||||||
|
const isSubmitting = ref(false)
|
||||||
|
const isSendingCode = ref(false)
|
||||||
|
|
||||||
let countdownTimer: ReturnType<typeof setInterval> | null = null
|
let countdownTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
@@ -71,6 +73,7 @@ async function handleSendCode() {
|
|||||||
errorMessage.value = '请先输入有效邮箱'
|
errorMessage.value = '请先输入有效邮箱'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
isSendingCode.value = true
|
||||||
try {
|
try {
|
||||||
const result = await authStore.sendCode(form.email.trim())
|
const result = await authStore.sendCode(form.email.trim())
|
||||||
successMessage.value = result.message || '验证码已发送'
|
successMessage.value = result.message || '验证码已发送'
|
||||||
@@ -81,6 +84,8 @@ async function handleSendCode() {
|
|||||||
startCooldown(retryAfter)
|
startCooldown(retryAfter)
|
||||||
}
|
}
|
||||||
errorMessage.value = error instanceof Error ? error.message : '验证码发送失败,请稍后重试'
|
errorMessage.value = error instanceof Error ? error.message : '验证码发送失败,请稍后重试'
|
||||||
|
} finally {
|
||||||
|
isSendingCode.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +102,7 @@ async function handleSubmit() {
|
|||||||
successMessage.value = ''
|
successMessage.value = ''
|
||||||
if (errorMessage.value) return
|
if (errorMessage.value) return
|
||||||
|
|
||||||
|
isSubmitting.value = true
|
||||||
try {
|
try {
|
||||||
await authStore.registerAccount({
|
await authStore.registerAccount({
|
||||||
email: form.email.trim(),
|
email: form.email.trim(),
|
||||||
@@ -107,6 +113,8 @@ async function handleSubmit() {
|
|||||||
await router.replace('/')
|
await router.replace('/')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error instanceof Error ? error.message : '注册失败,请稍后重试'
|
errorMessage.value = error instanceof Error ? error.message : '注册失败,请稍后重试'
|
||||||
|
} finally {
|
||||||
|
isSubmitting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +138,30 @@ onUnmounted(() => {
|
|||||||
<p class="brand-desc">
|
<p class="brand-desc">
|
||||||
只需几秒钟即可创建您的专属账号,体验下一代全网事件聚合与 AI 洞察服务。
|
只需几秒钟即可创建您的专属账号,体验下一代全网事件聚合与 AI 洞察服务。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div class="feature-list">
|
||||||
|
<div class="feature-item">
|
||||||
|
<div class="feature-icon">🔍</div>
|
||||||
|
<div class="feature-text">
|
||||||
|
<strong>个性化兴趣订阅</strong>
|
||||||
|
<span>定制你的专属信息流,只看你关心的动态</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-item">
|
||||||
|
<div class="feature-icon">📩</div>
|
||||||
|
<div class="feature-text">
|
||||||
|
<strong>每日邮件简报</strong>
|
||||||
|
<span>定时推送当日核心热点,重要信息不遗漏</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-item">
|
||||||
|
<div class="feature-icon">📊</div>
|
||||||
|
<div class="feature-text">
|
||||||
|
<strong>多维度事件追踪</strong>
|
||||||
|
<span>掌握事件起因、发展脉络与各方观点</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ambient-glow"></div>
|
<div class="ambient-glow"></div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -171,10 +203,10 @@ onUnmounted(() => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="input-action-btn"
|
class="input-action-btn"
|
||||||
:disabled="!canSendCode"
|
:disabled="!canSendCode || isSendingCode"
|
||||||
@click="handleSendCode"
|
@click="handleSendCode"
|
||||||
>
|
>
|
||||||
{{ countdown > 0 ? `${countdown}s 后重发` : '获取验证码' }}
|
{{ isSendingCode ? '发送中...' : (countdown > 0 ? `${countdown}s 后重发` : '获取验证码') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -189,9 +221,9 @@ onUnmounted(() => {
|
|||||||
:type="showPassword ? 'text' : 'password'"
|
:type="showPassword ? 'text' : 'password'"
|
||||||
placeholder="至少 8 位字符"
|
placeholder="至少 8 位字符"
|
||||||
/>
|
/>
|
||||||
<button type="button" class="input-action-btn" @click="showPassword = !showPassword">
|
<!-- <button type="button" class="input-action-btn" @click="showPassword = !showPassword">
|
||||||
{{ showPassword ? '隐藏' : '显示' }}
|
{{ showPassword ? '隐藏' : '显示' }}
|
||||||
</button>
|
</button> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -232,7 +264,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn-primary" :disabled="authStore.loading" type="submit">
|
<button class="btn-primary" :disabled="authStore.loading" type="submit">
|
||||||
{{ authStore.loading ? '提交中...' : '注册并登录' }}
|
{{ isSubmitting ? '提交中...' : '注册并登录' }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -258,11 +290,36 @@ onUnmounted(() => {
|
|||||||
.brand-panel {
|
.brand-panel {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: none;
|
display: none;
|
||||||
background: linear-gradient(135deg, var(--bg-surface), var(--bg-base));
|
background: var(--bg-surface);
|
||||||
border-right: 1px solid var(--border-subtle);
|
border-right: 1px solid var(--border-subtle);
|
||||||
padding: 80px;
|
padding: 80px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
/* 确保伪元素不会遮挡内容,且以正常图层渲染 */
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动态网格线背景,营造科技空间感 */
|
||||||
|
.brand-panel::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -50%; left: -50%; right: -50%; bottom: -50%;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(128, 128, 128, 0.15) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(128, 128, 128, 0.15) 1px, transparent 1px);
|
||||||
|
background-size: 32px 32px;
|
||||||
|
background-position: center center;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
/* 添加缓慢移动动画 */
|
||||||
|
animation: grid-move 20s linear infinite;
|
||||||
|
mask-image: radial-gradient(circle at center, black 40%, transparent 80%);
|
||||||
|
-webkit-mask-image: radial-gradient(circle at center, black 40%, transparent 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes grid-move {
|
||||||
|
0% { transform: translate(0, 0); }
|
||||||
|
100% { transform: translate(32px, 32px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 900px) {
|
@media (min-width: 900px) {
|
||||||
@@ -314,6 +371,60 @@ onUnmounted(() => {
|
|||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
transition: transform 0.3s ease, background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-text strong {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-text span {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ambient-glow {
|
.ambient-glow {
|
||||||
|
|||||||
Reference in New Issue
Block a user