Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 198ef7f1d5 | |||
| d016182c14 | |||
| 33578f9fe1 | |||
| c624a72124 | |||
| 2d20170ac7 | |||
| ff10aaaea8 |
2 changed files with 189 additions and 1 deletions
|
|
@ -2,8 +2,35 @@ import { StrictMode, useCallback, useEffect, useMemo, useRef, useState } from 'r
|
|||
import { createRoot } from 'react-dom/client'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { splitFrontmatter, documentOutline, jumpToHeading, MarkdownDocument, markdownHtml } from './modules/knowledge-render'
|
||||
import nightLake from './assets/hololake-night-lake.png'
|
||||
|
||||
const TAG_TINTS = ['tag-lavender', 'tag-sky', 'tag-mint', 'tag-amber', 'tag-rose', 'tag-slate']
|
||||
|
||||
// 编号自动补齐:人只敲字母与数字,杠由系统按已登记文法自动出。
|
||||
// ICE 系(ICE-GL∞ / ICE-P-ZY001)、GLS 系(GLS-LA-20260720-003);认不出的文法只在字母数字交界处补杠。
|
||||
function formatGateNumber(raw: string): string {
|
||||
const up = raw.toUpperCase()
|
||||
if (up.startsWith('ICE')) {
|
||||
const rest = up.slice(3)
|
||||
if (!rest) return 'ICE'
|
||||
if (rest.startsWith('P')) {
|
||||
const body = rest.slice(1)
|
||||
return body ? `ICE-P-${body}` : 'ICE-P'
|
||||
}
|
||||
return `ICE-${rest}`
|
||||
}
|
||||
if (up.startsWith('GLS')) {
|
||||
const rest = up.slice(3)
|
||||
if (!rest) return 'GLS'
|
||||
const letters = rest.match(/^[A-Z]*/)?.[0] ?? ''
|
||||
const digits = rest.slice(letters.length)
|
||||
if (!digits) return `GLS-${letters}`
|
||||
const d1 = digits.slice(0, 8)
|
||||
const d2 = digits.slice(8, 11)
|
||||
return `GLS-${letters}-${d1}${d2 ? `-${d2}` : ''}`
|
||||
}
|
||||
return up.replace(/([A-Z]+)([0-9])/g, '$1-$2').replace(/([0-9]+)([A-Z])/g, '$1-$2')
|
||||
}
|
||||
function tagTint(tag: string) {
|
||||
let hash = 0
|
||||
for (const char of tag) hash = (hash * 31 + (char.codePointAt(0) || 0)) % 997
|
||||
|
|
@ -257,6 +284,21 @@ function HoloLakeApp() {
|
|||
const [loginBusy, setLoginBusy] = useState(false)
|
||||
const [loginRising, setLoginRising] = useState(false)
|
||||
const [loginMessage, setLoginMessage] = useState('')
|
||||
const [gateStage, setGateStage] = useState<'number' | 'key'>('number')
|
||||
const [gateRaw, setGateRaw] = useState('')
|
||||
const [gateInf, setGateInf] = useState(false)
|
||||
const gateNumber = formatGateNumber(gateRaw) + (gateInf ? '∞' : '')
|
||||
const [gateOpen, setGateOpen] = useState(false)
|
||||
const gateInputRef = useRef<HTMLInputElement>(null)
|
||||
// 封存舱点开翻转后半程再把光标送进输入格,免得翻到一半抢焦点
|
||||
useEffect(() => {
|
||||
if (!gateOpen || gateStage !== 'number') return
|
||||
const timer = window.setTimeout(() => gateInputRef.current?.focus(), 640)
|
||||
return () => window.clearTimeout(timer)
|
||||
}, [gateOpen, gateStage])
|
||||
const [gateBusy, setGateBusy] = useState(false)
|
||||
const [gateMessage, setGateMessage] = useState('')
|
||||
const [gateRising, setGateRising] = useState(false)
|
||||
const [systemBusy, setSystemBusy] = useState(false)
|
||||
const [zeroPoint, setZeroPoint] = useState<ZeroPointSnapshot | null>(null)
|
||||
const [zpNumber, setZpNumber] = useState('')
|
||||
|
|
@ -313,6 +355,39 @@ function HoloLakeApp() {
|
|||
if (zeroPoint.route !== 'persona' && view === 'parlor') setView('generic')
|
||||
if (zeroPoint.route === 'persona' && view === 'generic') setView('knowledge')
|
||||
}, [zeroPoint, view])
|
||||
// 大门·编号门:本机已有绑定编号时回填,省得主人重报家门
|
||||
useEffect(() => {
|
||||
if (gateStage !== 'number' || gateRaw) return
|
||||
const bound = zeroPoint?.binding === 'bound' ? zeroPoint.userNumber : ''
|
||||
if (!bound) return
|
||||
setGateInf(bound.includes('∞'))
|
||||
setGateRaw(bound.toUpperCase().replace(/[^A-Z0-9]/g, ''))
|
||||
}, [zeroPoint, gateStage, gateRaw])
|
||||
// 大门·编号门输入:只收字母数字(杠系统补、∞开关点),删到∞时开关跟着灭
|
||||
const onGateInput = (value: string) => {
|
||||
if (gateInf && !value.includes('∞')) setGateInf(false)
|
||||
setGateRaw(value.toUpperCase().replace(/[^A-Z0-9]/g, ''))
|
||||
}
|
||||
// 大门·编号门:先报编号→灯塔查号→只答有效/无效→域浮起→才见钥匙门
|
||||
const gateVerifyNumber = async () => {
|
||||
setGateBusy(true)
|
||||
setGateMessage('')
|
||||
try {
|
||||
await invoke<ZeroPointSnapshot>('zero_point_bind', { input: { number: gateNumber } })
|
||||
const snapshot = await invoke<ZeroPointSnapshot>('zero_point_verify')
|
||||
setZeroPoint(snapshot)
|
||||
if (snapshot.route === 'persona') {
|
||||
setGateRising(true)
|
||||
window.setTimeout(() => { setGateRising(false); setGateStage('key') }, 2600)
|
||||
} else {
|
||||
setGateMessage('编号无效')
|
||||
}
|
||||
} catch (error) {
|
||||
setGateMessage(humanError(error, 'login'))
|
||||
} finally {
|
||||
setGateBusy(false)
|
||||
}
|
||||
}
|
||||
useEffect(() => { if (view === 'receipts') void loadReceipts() }, [loadReceipts, view])
|
||||
|
||||
const visibleDocuments = useMemo(() => {
|
||||
|
|
@ -877,7 +952,49 @@ function HoloLakeApp() {
|
|||
|
||||
if (!repoLogin) {
|
||||
return <div className="app-shell">
|
||||
<div className={`onboarding-backdrop lake-scene${loginRising ? ' sinking' : ''}`}><div className="lake-bays" aria-hidden="true">{([['main', '主域'], ['sub', '分域'], ['zero', '零域'], ['sense', '零感域'], ['fifth', '第五域']] as const).map(([bayId, bayName]) => <span key={bayId} className={`lake-bay ${bayId}${loginRising && bayId === 'fifth' ? ' rising' : ''}`}><i/><b>{bayName}</b></span>)}</div><section className={`onboarding-card${loginRising ? ' fade-out' : ''}`} role="dialog"><span className="onboarding-mark">光湖</span><span className="kicker">CODE REPOSITORY ENTRY</span><h1>登录光湖代码频道</h1><p>使用第五域代码仓库(guanghulab.com)的账号与密码登录。登录即验证了背后绑定的服务器;凭证只存本机钥匙串,不落明文。</p><form onSubmit={(event) => void performRepoLogin(event)}><label htmlFor="repo-login-username">仓库账号</label><input id="repo-login-username" autoFocus maxLength={40} value={loginUsername} placeholder="代码仓库用户名" onChange={(event) => setLoginUsername(event.target.value)}/><label htmlFor="repo-login-password">密码</label><input id="repo-login-password" type="password" maxLength={512} value={loginPassword} placeholder="代码仓库密码" onChange={(event) => setLoginPassword(event.target.value)}/><button className="primary-button" disabled={loginBusy || !loginUsername.trim() || !loginPassword}>{loginBusy ? '正在验证…' : '进入 HoloLake'}</button></form>{loginMessage && <p>{loginMessage}</p>}</section></div>
|
||||
<div className={`gate-lake${loginRising ? ' sinking' : ''}`} style={{ '--lake-img': `url(${nightLake})` } as React.CSSProperties}>
|
||||
<i className="lake-mist" aria-hidden="true"/>
|
||||
<div className="lake-lights" aria-hidden="true">{[1, 2, 3, 4, 5].map((n) => <span key={n} className={`lake-light l${n}${(gateRising || loginRising) && n === 5 ? ' rising' : ''}`}/>)}</div>
|
||||
{<header className={`gate-hero${gateRising ? ' veil' : ''}`} aria-hidden={gateRising || undefined}>
|
||||
<span className="gate-emblem" aria-hidden="true"><i/></span>
|
||||
<b className="gate-product">HoloLake</b>
|
||||
<small className="gate-techname">光湖语言系统 · 通用人工智能操作平台</small>
|
||||
<small className="gate-abbr">GH-AIOS · GuangHu AI Operating System</small>
|
||||
</header>}
|
||||
{gateRising ? (
|
||||
<section className="gate-rise" role="status">
|
||||
<span className="gate-rise-star" aria-hidden="true"><i/></span>
|
||||
<h1>第五域 · 光之湖</h1>
|
||||
<p>欢迎回来 · {gateNumber}</p>
|
||||
</section>
|
||||
) : gateStage === 'number' ? (
|
||||
<section className={`gate-pod gate-flip${gateOpen ? ' open' : ''}${loginRising ? ' fade-out' : ''}`} role={gateOpen ? 'dialog' : 'button'} tabIndex={gateOpen ? undefined : 0} aria-expanded={gateOpen} onClick={gateOpen ? undefined : () => setGateOpen(true)} onKeyDown={gateOpen ? undefined : (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); setGateOpen(true) } }}>
|
||||
<div className="pod-face pod-sealed" aria-hidden={gateOpen}>
|
||||
<span className="gate-emblem sealed-emblem" aria-hidden="true"><i/></span>
|
||||
<b className="sealed-title">编号校验</b>
|
||||
<small className="sealed-sub">轻点翻启</small>
|
||||
</div>
|
||||
<div className="pod-face pod-form">
|
||||
<div className="gate-pod-row">
|
||||
<input id="gate-number" ref={gateInputRef} aria-label="编号" maxLength={48} value={gateNumber} placeholder="如 ICE-GL∞" onChange={(event) => onGateInput(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && gateNumber) { event.preventDefault(); void gateVerifyNumber() } }}/>
|
||||
<button type="button" className={`gate-inf${gateInf ? ' on' : ''}`} title="编号里带无限符号就点一下点亮" aria-pressed={gateInf} onClick={() => setGateInf((v) => !v)}>∞</button>
|
||||
</div>
|
||||
<button className="gate-submit" disabled={gateBusy || !gateNumber} onClick={() => void gateVerifyNumber()}>{gateBusy ? '查验中…' : '提交'}</button>
|
||||
{gateMessage && <p className="gate-hint">{gateMessage}</p>}
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section className={`gate-pod gate-pod-key${loginRising ? ' fade-out' : ''}`} role="dialog">
|
||||
<form onSubmit={(event) => void performRepoLogin(event)}>
|
||||
<input id="repo-login-username" aria-label="账号" autoFocus maxLength={40} value={loginUsername} placeholder="账号" onChange={(event) => setLoginUsername(event.target.value)}/>
|
||||
<input id="repo-login-password" aria-label="密码" type="password" maxLength={512} value={loginPassword} placeholder="密码" onChange={(event) => setLoginPassword(event.target.value)}/>
|
||||
<button className="gate-submit" disabled={loginBusy || !loginUsername.trim() || !loginPassword}>{loginBusy ? '查验中…' : '进入'}</button>
|
||||
</form>
|
||||
{loginMessage && <p className="gate-hint">{loginMessage}</p>}
|
||||
<button className="gate-back" type="button" onClick={() => { setGateStage('number'); setGateMessage(''); setLoginMessage('') }}>换编号</button>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -436,3 +436,74 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
@keyframes bay-rise { 0% { transform: translateY(0) scale(1); } 55% { transform: translateY(-52px) scale(1.2); } 100% { transform: translateY(-150px) scale(1.9); } }
|
||||
.onboarding-card { position: relative; z-index: 1; transition: opacity .7s ease, transform .7s ease; }
|
||||
.onboarding-card.fade-out { opacity: 0; transform: translateY(16px) scale(.985); }
|
||||
|
||||
/* ===== 光湖世界大门 v2:一片湖 · 五块沉底玉 · 一只悬浮编号舱 ===== */
|
||||
.gate-lake { position: fixed; inset: 0; z-index: 20; overflow: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: clamp(26px, 5vh, 54px); background-image: linear-gradient(180deg, color-mix(in srgb, var(--surface-depth) 62%, transparent), color-mix(in srgb, #04070d 78%, transparent) 88%), var(--lake-img); background-size: cover, cover; background-position: center 30%, center; transition: opacity .9s ease; }
|
||||
.gate-lake.sinking { opacity: 0; }
|
||||
.lake-mist { position: absolute; inset: 0; pointer-events: none; background: radial-gradient(60% 34% at 50% 68%, color-mix(in srgb, var(--primitive-cool-glow) 13%, transparent), transparent 72%); animation: mist-drift 11s ease-in-out infinite; }
|
||||
@keyframes mist-drift { 0%, 100% { opacity: .55; transform: translateX(0); } 50% { opacity: .95; transform: translateX(2.4%); } }
|
||||
|
||||
/* 沉湖星子:光湖是语言宇宙·星系——五点微光沉在水下,轻微起伏 */
|
||||
.lake-lights { position: absolute; left: 0; right: 0; bottom: 21%; display: flex; align-items: flex-end; justify-content: center; gap: clamp(46px, 9vw, 150px); pointer-events: none; }
|
||||
.lake-light { position: relative; width: 42px; height: 42px; border-radius: 50%; transform: translateY(52%); background: radial-gradient(circle at 50% 36%, color-mix(in srgb, var(--primitive-cool-glow) 82%, transparent), color-mix(in srgb, var(--primitive-cool-glow) 28%, transparent) 50%, transparent 74%); filter: blur(.8px); opacity: .42; animation: light-bob 7.5s ease-in-out infinite; transition: transform 1.2s cubic-bezier(.3, .7, .35, 1), opacity 1.2s ease, filter 1.2s ease; }
|
||||
.lake-light.l1 { animation-delay: .2s; } .lake-light.l2 { animation-delay: 1.6s; width: 34px; height: 34px; } .lake-light.l3 { animation-delay: 3s; } .lake-light.l4 { animation-delay: 1s; width: 36px; height: 36px; }
|
||||
.lake-light.l5 { animation-delay: 2.2s; width: 58px; height: 58px; transform: translateY(46%); background: radial-gradient(circle at 50% 36%, color-mix(in srgb, var(--primitive-warm-glow) 92%, #fff), color-mix(in srgb, var(--primitive-warm-glow) 36%, transparent) 48%, transparent 74%); opacity: .68; filter: blur(.4px) drop-shadow(0 -6px 34px color-mix(in srgb, var(--primitive-warm-glow) 34%, transparent)); }
|
||||
.lake-light.rising { transform: translateY(-150%) scale(1.5); opacity: 1; filter: blur(0) drop-shadow(0 0 42px color-mix(in srgb, var(--primitive-warm-glow) 62%, transparent)); animation: none; }
|
||||
@keyframes light-bob { 0%, 100% { translate: 0 0; } 50% { translate: 0 -7px; } }
|
||||
|
||||
/* 编号舱:湖面正中一只悬浮玻璃舱,只装编号 */
|
||||
.gate-pod { position: relative; z-index: 3; width: min(400px, 88vw); padding: 26px 30px 28px; text-align: center; border: 1px solid color-mix(in srgb, var(--accent-light) 22%, transparent); border-radius: 24px; background: color-mix(in srgb, var(--panel-bg) 62%, transparent); backdrop-filter: blur(26px) saturate(1.15); box-shadow: 0 34px 90px rgba(0, 0, 0, .5), 0 0 70px color-mix(in srgb, var(--primitive-warm-glow) 9%, transparent), inset 0 1px 0 rgba(255, 255, 255, .07); animation: pod-float 6.5s ease-in-out infinite; transition: opacity .7s ease, transform .7s ease; }
|
||||
.gate-pod.fade-out { opacity: 0; transform: translateY(18px) scale(.985); }
|
||||
@keyframes pod-float { 0%, 100% { translate: 0 0; } 50% { translate: 0 -9px; } }
|
||||
|
||||
/* 封存翻转舱:湖面上一枚封存徽舱,轻点一下翻转翻开表单面 */
|
||||
.gate-flip { display: grid; transform-style: preserve-3d; transition: transform .95s cubic-bezier(.32, .72, .3, 1); cursor: pointer; background: transparent; border-color: transparent; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none; }
|
||||
.gate-flip .pod-face { border: 1px solid color-mix(in srgb, var(--accent-light) 22%, transparent); border-radius: 24px; background: color-mix(in srgb, var(--panel-bg) 62%, transparent); backdrop-filter: blur(26px) saturate(1.15); -webkit-backdrop-filter: blur(26px) saturate(1.15); box-shadow: 0 34px 90px rgba(0, 0, 0, .5), 0 0 70px color-mix(in srgb, var(--primitive-warm-glow) 9%, transparent), inset 0 1px 0 rgba(255, 255, 255, .07); }
|
||||
.gate-flip.open { transform: rotateY(180deg); cursor: default; }
|
||||
.pod-face { grid-area: 1 / 1; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
|
||||
.pod-form { transform: rotateY(180deg); }
|
||||
.pod-sealed { display: grid; justify-items: center; align-content: center; gap: 11px; padding: 20px 6px 14px; }
|
||||
.sealed-emblem { width: 86px; height: 86px; margin-bottom: 4px; }
|
||||
.sealed-emblem::before { box-shadow: 0 0 30px color-mix(in srgb, var(--primitive-warm-glow) 16%, transparent); }
|
||||
.sealed-title { color: var(--content-primary); font-size: 16px; font-weight: 600; letter-spacing: .44em; text-indent: .44em; }
|
||||
.sealed-sub { color: var(--content-faint); font-size: 11px; letter-spacing: .32em; text-indent: .32em; }
|
||||
.gate-flip:not(.open):hover .sealed-emblem::before { border-color: color-mix(in srgb, var(--primitive-warm-glow) 46%, transparent); box-shadow: 0 0 40px color-mix(in srgb, var(--primitive-warm-glow) 32%, transparent); }
|
||||
.gate-flip:not(.open):hover .sealed-sub { color: var(--content-muted); }
|
||||
.gate-hero { position: relative; z-index: 2; display: grid; justify-items: center; gap: 10px; text-align: center; padding: 0 24px; transition: opacity 1.1s ease, transform 1.1s ease, filter 1.1s ease; }
|
||||
.gate-hero.veil { opacity: 0; transform: translateY(-14px) scale(1.03); filter: blur(4px); pointer-events: none; }
|
||||
.gate-hero > * { animation: hero-in 1.1s ease both; }
|
||||
.gate-hero > *:nth-child(2) { animation-delay: .15s; }
|
||||
.gate-hero > *:nth-child(3) { animation-delay: .32s; }
|
||||
.gate-hero > *:nth-child(4) { animation-delay: .48s; }
|
||||
@keyframes hero-in { 0% { opacity: 0; transform: translateY(16px); } 100% { opacity: 1; transform: translateY(0); } }
|
||||
.gate-emblem { position: relative; width: 62px; height: 62px; margin-bottom: 6px; }
|
||||
.gate-emblem::before { content: ''; position: absolute; inset: 0; border: 1px solid color-mix(in srgb, var(--accent-light) 30%, transparent); border-radius: 50%; box-shadow: 0 0 22px color-mix(in srgb, var(--primitive-warm-glow) 12%, transparent); }
|
||||
.gate-emblem::after { content: ''; position: absolute; left: 50%; top: 50%; width: 9px; height: 9px; transform: translate(-50%, -50%); border-radius: 50%; background: var(--primitive-warm-glow); box-shadow: 0 0 18px color-mix(in srgb, var(--primitive-warm-glow) 82%, transparent); animation: emblem-star 4s ease-in-out infinite; }
|
||||
.gate-emblem i { position: absolute; left: -22%; right: -22%; top: 64%; height: 1px; background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent-light) 52%, transparent), transparent); }
|
||||
@keyframes emblem-star { 0%, 100% { opacity: .75; } 50% { opacity: 1; } }
|
||||
.gate-product { color: var(--content-primary); font-size: clamp(40px, 5.2vw, 54px); font-weight: 640; letter-spacing: .22em; text-indent: .22em; text-shadow: 0 0 44px color-mix(in srgb, var(--primitive-warm-glow) 26%, transparent); }
|
||||
.gate-techname { color: var(--content-secondary); font-size: 14.5px; letter-spacing: .24em; text-indent: .24em; }
|
||||
.gate-abbr { color: var(--content-faint); font-size: 11px; letter-spacing: .3em; text-indent: .3em; text-transform: uppercase; }
|
||||
.gate-pod-row { display: flex; gap: 10px; }
|
||||
.gate-pod input { flex: 1; height: 52px; padding: 0 16px; border: 1px solid color-mix(in srgb, var(--accent-light) 24%, transparent); border-radius: 14px; outline: 0; color: var(--content-primary); background: rgba(0, 0, 0, .22); font-size: 17px; letter-spacing: .08em; text-align: center; transition: border-color .3s ease, box-shadow .3s ease; }
|
||||
.gate-pod input:focus { border-color: color-mix(in srgb, var(--primitive-warm-glow) 58%, transparent); box-shadow: 0 0 22px color-mix(in srgb, var(--primitive-warm-glow) 18%, transparent); }
|
||||
.gate-pod input::placeholder { color: var(--content-faint); letter-spacing: .2em; }
|
||||
.gate-pod form { display: grid; gap: 12px; }
|
||||
.gate-inf { width: 52px; height: 52px; flex: 0 0 auto; border: 1px solid color-mix(in srgb, var(--accent-light) 24%, transparent); border-radius: 14px; background: rgba(0, 0, 0, .22); color: var(--content-faint); font-size: 21px; cursor: pointer; transition: all .35s ease; }
|
||||
.gate-inf:hover { color: var(--content-secondary); border-color: color-mix(in srgb, var(--primitive-warm-glow) 40%, transparent); }
|
||||
.gate-inf.on { color: #0b0f14; background: linear-gradient(150deg, color-mix(in srgb, var(--primitive-warm-glow) 92%, #fff), var(--primitive-warm-glow)); border-color: transparent; box-shadow: 0 0 30px color-mix(in srgb, var(--primitive-warm-glow) 52%, transparent); }
|
||||
.gate-submit { height: 48px; margin-top: 16px; border: 0; border-radius: 14px; background: linear-gradient(140deg, color-mix(in srgb, var(--primitive-warm-glow) 78%, #fff) 0%, color-mix(in srgb, var(--primitive-warm-glow) 62%, #7a5a22) 100%); color: #141008; font-size: 15px; font-weight: 650; letter-spacing: .3em; text-indent: .3em; cursor: pointer; transition: filter .3s ease, opacity .3s ease, box-shadow .3s ease; box-shadow: 0 10px 30px color-mix(in srgb, var(--primitive-warm-glow) 22%, transparent); }
|
||||
.gate-submit:hover:not(:disabled) { filter: brightness(1.07); box-shadow: 0 12px 40px color-mix(in srgb, var(--primitive-warm-glow) 34%, transparent); }
|
||||
.gate-submit:disabled { opacity: .45; cursor: default; box-shadow: none; }
|
||||
.gate-hint { margin-top: 14px; color: var(--content-muted); font-size: 13px; }
|
||||
.gate-back { margin-top: 14px; border: 0; background: none; color: var(--content-faint); font-size: 12.5px; letter-spacing: .1em; cursor: pointer; transition: color .25s ease; }
|
||||
.gate-back:hover { color: var(--accent-light); }
|
||||
|
||||
/* 过关:玉自湖面升起 → 欢迎语浮现 */
|
||||
.gate-rise { position: relative; z-index: 3; display: grid; place-items: center; text-align: center; }
|
||||
.gate-rise-star { display: block; margin-bottom: 26px; animation: gate-jade-rise 2.3s cubic-bezier(.28, .68, .32, 1) both; }
|
||||
.gate-rise-star i { display: block; width: 88px; height: 88px; border-radius: 50%; background: radial-gradient(circle at 50% 38%, #fff8e6, color-mix(in srgb, var(--primitive-warm-glow) 88%, transparent) 32%, color-mix(in srgb, var(--primitive-warm-glow) 30%, transparent) 60%, transparent 78%); filter: drop-shadow(0 0 60px color-mix(in srgb, var(--primitive-warm-glow) 64%, transparent)); }
|
||||
@keyframes gate-jade-rise { 0% { transform: translateY(46vh) scale(.55); opacity: 0; } 30% { opacity: 1; } 100% { transform: translateY(0) scale(1); opacity: 1; } }
|
||||
.gate-rise h1 { margin: 0; color: var(--content-primary); font-size: clamp(30px, 4.4vw, 42px); font-weight: 650; letter-spacing: .1em; text-shadow: 0 0 46px color-mix(in srgb, var(--primitive-warm-glow) 36%, transparent); animation: gate-text-in 1s ease 1s both; }
|
||||
.gate-rise p { margin: 14px 0 0; color: var(--content-muted); font-size: 14.5px; letter-spacing: .14em; animation: gate-text-in 1s ease 1.5s both; }
|
||||
@keyframes gate-text-in { 0% { opacity: 0; transform: translateY(14px); } 100% { opacity: 1; transform: translateY(0); } }
|
||||
|
|
|
|||
Loading…
Reference in a new issue