feat: 大门v2唯美重绘——一片湖+五块沉底玉(只露尖微动)+湖面正中悬浮编号舱;编号自动补杠(ICE/GLS登记文法)+∞开关点亮;过关玉升起→欢迎语→钥匙门;文字大删改画面叙事(冰朔20260816谕:对外分发的软件要动态画面代替文字)

This commit is contained in:
冰朔 2026-08-16 01:25:09 +08:00
commit 2d20170ac7
2 changed files with 111 additions and 63 deletions

View file

@ -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
@ -258,7 +285,9 @@ function HoloLakeApp() {
const [loginRising, setLoginRising] = useState(false)
const [loginMessage, setLoginMessage] = useState('')
const [gateStage, setGateStage] = useState<'number' | 'key'>('number')
const [gateNumber, setGateNumber] = useState('')
const [gateRaw, setGateRaw] = useState('')
const [gateInf, setGateInf] = useState(false)
const gateNumber = formatGateNumber(gateRaw) + (gateInf ? '∞' : '')
const [gateBusy, setGateBusy] = useState(false)
const [gateMessage, setGateMessage] = useState('')
const [gateRising, setGateRising] = useState(false)
@ -320,21 +349,30 @@ function HoloLakeApp() {
}, [zeroPoint, view])
// 大门·编号门:本机已有绑定编号时回填,省得主人重报家门
useEffect(() => {
if (gateStage === 'number' && !gateNumber && zeroPoint?.binding === 'bound' && zeroPoint.userNumber) setGateNumber(zeroPoint.userNumber)
}, [zeroPoint, gateStage, gateNumber])
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.trim() } })
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') }, 1900)
window.setTimeout(() => { setGateRising(false); setGateStage('key') }, 2600)
} else {
setGateMessage('编号未获灯塔放行——灯塔只答有效或无效,不透露编号表。')
setGateMessage('编号无效')
}
} catch (error) {
setGateMessage(humanError(error, 'login'))
@ -906,45 +944,35 @@ 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}${(gateRising || loginRising) && bayId === 'fifth' ? ' rising' : ''}`}><i/><b>{bayName}</b></span>)}</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-jades" aria-hidden="true">{[1, 2, 3, 4, 5].map((n) => <span key={n} className={`jade j${n}${(gateRising || loginRising) && n === 5 ? ' rising' : ''}`}/>)}</div>
{gateRising ? (
<section className="gate-welcome" role="status">
<span className="gate-welcome-kicker"> · </span>
<h1> · </h1>
<p> {gateNumber} </p>
<section className="gate-rise" role="status">
<span className="gate-rise-jade" aria-hidden="true"><i/></span>
<h1> · </h1>
<p> · {gateNumber}</p>
</section>
) : gateStage === 'number' ? (
<section className={`gate-pod${loginRising ? ' fade-out' : ''}`} role="dialog">
<span className="gate-pod-mark"></span>
<div className="gate-pod-row">
<input id="gate-number" aria-label="编号" autoFocus maxLength={48} value={gateNumber} placeholder="报上编号" 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>}
</section>
) : (
<section className={`onboarding-card gate-card${loginRising ? ' fade-out' : ''}`} role="dialog">
<span className="onboarding-mark gate-mark"></span>
<div className="gate-steps" aria-hidden="true"><span className={gateStage === 'number' ? 'active' : 'done'}><i></i></span><em/><span className={gateStage === 'key' ? 'active' : ''}><i></i></span></div>
{gateStage === 'number' ? (
<>
<span className="kicker">GATE OF GUANGHU WORLD</span>
<h1></h1>
<p>"有效 / 无效"</p>
<form onSubmit={(event) => { event.preventDefault(); void gateVerifyNumber() }}>
<label htmlFor="gate-number"></label>
<input id="gate-number" autoFocus maxLength={40} value={gateNumber} placeholder="如 ICE-GL∞" onChange={(event) => setGateNumber(event.target.value)}/>
<button className="primary-button" disabled={gateBusy || !gateNumber.trim()}>{gateBusy ? '灯塔查号中…' : '校验编号'}</button>
</form>
</>
) : (
<>
<span className="kicker">FIFTH DOMAIN · KEY GATE</span>
<h1> · </h1>
<p> <b className="gate-number-chip">{gateNumber}</b> 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>
<button className="gate-back" type="button" onClick={() => { setGateStage('number'); setGateMessage(''); setLoginMessage('') }}></button>
</>
)}
{(gateMessage || loginMessage) && <p className="gate-message">{gateMessage || loginMessage}</p>}
<section className={`gate-pod gate-pod-key${loginRising ? ' fade-out' : ''}`} role="dialog">
<span className="gate-pod-mark"></span>
<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>