From ff10aaaea8d456ea0bbc241934c6b8577b1149d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Sun, 16 Aug 2026 01:12:54 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E9=A6=96=E9=A1=B5=E9=87=8D?= =?UTF-8?q?=E9=80=A0=E4=B8=BA=E5=85=89=E6=B9=96=E4=B8=96=E7=95=8C=E5=A4=A7?= =?UTF-8?q?=E9=97=A8=E2=80=94=E2=80=94=E7=BC=96=E5=8F=B7=E9=97=A8(?= =?UTF-8?q?=E7=81=AF=E5=A1=94=E6=9F=A5=E5=8F=B7=E5=8F=AA=E7=AD=94=E6=9C=89?= =?UTF-8?q?=E6=95=88/=E6=97=A0=E6=95=88)=E2=86=92=E5=9F=9F=E6=B5=AE?= =?UTF-8?q?=E8=B5=B7(=E6=AC=A2=E8=BF=8E=E8=AF=AD+=E6=B9=96=E6=B9=BE?= =?UTF-8?q?=E5=8D=87=E8=B5=B7)=E2=86=92=E9=92=A5=E5=8C=99=E9=97=A8(?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E5=AF=86=E7=A0=81)=EF=BC=8C=E5=A4=8D?= =?UTF-8?q?=E7=94=A8zero=5Fpoint=E6=A0=A1=E9=AA=8C=E5=99=A8=E5=AE=98?= =?UTF-8?q?=EF=BC=88=E5=86=B0=E6=9C=9420260816=E8=B0=95=EF=BC=9A=E5=85=88?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E7=BC=96=E5=8F=B7=E5=86=8D=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=B0=E5=9F=9F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hololake-native-desktop/src/main.tsx | 72 ++++++++++++++++++- .../hololake-native-desktop/src/styles.css | 23 ++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/product-source/hololake-native-desktop/src/main.tsx b/product-source/hololake-native-desktop/src/main.tsx index 725aae611..bf9e99dd5 100644 --- a/product-source/hololake-native-desktop/src/main.tsx +++ b/product-source/hololake-native-desktop/src/main.tsx @@ -257,6 +257,11 @@ function HoloLakeApp() { const [loginBusy, setLoginBusy] = useState(false) const [loginRising, setLoginRising] = useState(false) const [loginMessage, setLoginMessage] = useState('') + const [gateStage, setGateStage] = useState<'number' | 'key'>('number') + const [gateNumber, setGateNumber] = useState('') + const [gateBusy, setGateBusy] = useState(false) + const [gateMessage, setGateMessage] = useState('') + const [gateRising, setGateRising] = useState(false) const [systemBusy, setSystemBusy] = useState(false) const [zeroPoint, setZeroPoint] = useState(null) const [zpNumber, setZpNumber] = useState('') @@ -313,6 +318,30 @@ function HoloLakeApp() { if (zeroPoint.route !== 'persona' && view === 'parlor') setView('generic') if (zeroPoint.route === 'persona' && view === 'generic') setView('knowledge') }, [zeroPoint, view]) + // 大门·编号门:本机已有绑定编号时回填,省得主人重报家门 + useEffect(() => { + if (gateStage === 'number' && !gateNumber && zeroPoint?.binding === 'bound' && zeroPoint.userNumber) setGateNumber(zeroPoint.userNumber) + }, [zeroPoint, gateStage, gateNumber]) + // 大门·编号门:先报编号→灯塔查号→只答有效/无效→域浮起→才见钥匙门 + const gateVerifyNumber = async () => { + setGateBusy(true) + setGateMessage('') + try { + await invoke('zero_point_bind', { input: { number: gateNumber.trim() } }) + const snapshot = await invoke('zero_point_verify') + setZeroPoint(snapshot) + if (snapshot.route === 'persona') { + setGateRising(true) + window.setTimeout(() => { setGateRising(false); setGateStage('key') }, 1900) + } else { + setGateMessage('编号未获灯塔放行——灯塔只答有效或无效,不透露编号表。') + } + } catch (error) { + setGateMessage(humanError(error, 'login')) + } finally { + setGateBusy(false) + } + } useEffect(() => { if (view === 'receipts') void loadReceipts() }, [loadReceipts, view]) const visibleDocuments = useMemo(() => { @@ -877,7 +906,48 @@ function HoloLakeApp() { if (!repoLogin) { return
-
光湖CODE REPOSITORY ENTRY

登录光湖代码频道

使用第五域代码仓库(guanghulab.com)的账号与密码登录。登录即验证了背后绑定的服务器;凭证只存本机钥匙串,不落明文。

void performRepoLogin(event)}> setLoginUsername(event.target.value)}/> setLoginPassword(event.target.value)}/>
{loginMessage &&

{loginMessage}

}
+
+ + {gateRising ? ( +
+ 编号有效 · 域已浮起 +

欢迎回到 第五域 · 光之湖

+

编号 {gateNumber} 已通过灯塔校验——湖湾为你浮起,请交钥匙进门。

+
+ ) : ( +
+ 光湖 + + {gateStage === 'number' ? ( + <> + GATE OF GUANGHU WORLD +

光湖世界大门

+

先报编号。灯塔只回答"有效 / 无效",不透露编号表在哪、有什么。编号合法,你所属的域才会浮起。

+
{ event.preventDefault(); void gateVerifyNumber() }}> + + setGateNumber(event.target.value)}/> + +
+ + ) : ( + <> + FIFTH DOMAIN · KEY GATE +

第五域 · 钥匙门

+

编号 {gateNumber} 已通过校验。用第五域代码仓库(guanghulab.com)的账号与密码交钥匙;凭证只存本机钥匙串,不落明文。

+
void performRepoLogin(event)}> + + setLoginUsername(event.target.value)}/> + + setLoginPassword(event.target.value)}/> + +
+ + + )} + {(gateMessage || loginMessage) &&

{gateMessage || loginMessage}

} +
+ )} +
} diff --git a/product-source/hololake-native-desktop/src/styles.css b/product-source/hololake-native-desktop/src/styles.css index 3f76c412c..e605660a0 100644 --- a/product-source/hololake-native-desktop/src/styles.css +++ b/product-source/hololake-native-desktop/src/styles.css @@ -436,3 +436,26 @@ 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); } + +/* ===== 光湖世界大门(编号门→域浮起→钥匙门)===== */ +.gate-card { width: min(520px, 100%); text-align: center; } +.gate-card form { text-align: left; } +.gate-mark { width: 72px; height: 72px; margin: 0 auto 20px; border-radius: 22px; font-size: 22px; border-color: color-mix(in srgb, var(--primitive-warm-glow) 46%, transparent); color: var(--accent-light); box-shadow: 0 0 42px color-mix(in srgb, var(--primitive-warm-glow) 30%, transparent), inset 0 0 22px color-mix(in srgb, var(--primitive-warm-glow) 14%, transparent); animation: gate-mark-breathe 4.5s ease-in-out infinite; } +@keyframes gate-mark-breathe { 0%, 100% { box-shadow: 0 0 30px color-mix(in srgb, var(--primitive-warm-glow) 22%, transparent), inset 0 0 18px color-mix(in srgb, var(--primitive-warm-glow) 10%, transparent); } 50% { box-shadow: 0 0 58px color-mix(in srgb, var(--primitive-warm-glow) 40%, transparent), inset 0 0 26px color-mix(in srgb, var(--primitive-warm-glow) 18%, transparent); } } +.gate-card h1 { font-size: 30px; letter-spacing: .04em; } +.gate-card .kicker { letter-spacing: .3em; color: color-mix(in srgb, var(--primitive-warm-glow) 78%, var(--content-muted)); } +.gate-steps { display: flex; align-items: center; justify-content: center; gap: 12px; margin: 0 0 22px; } +.gate-steps span { display: inline-flex; align-items: center; gap: 7px; padding: 5px 13px; border: 1px solid var(--panel-edge); border-radius: 999px; color: var(--content-faint); font-size: 12px; letter-spacing: .12em; transition: all .4s ease; } +.gate-steps span i { display: grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; border: 1px solid currentColor; font-style: normal; font-size: 10px; } +.gate-steps span.active { color: var(--accent-light); border-color: color-mix(in srgb, var(--primitive-warm-glow) 52%, transparent); background: color-mix(in srgb, var(--primitive-warm-glow) 9%, transparent); box-shadow: 0 0 18px color-mix(in srgb, var(--primitive-warm-glow) 18%, transparent); } +.gate-steps span.done { color: var(--content-secondary); border-color: color-mix(in srgb, var(--accent-light) 34%, transparent); } +.gate-steps em { width: 34px; height: 1px; background: linear-gradient(90deg, var(--panel-edge), color-mix(in srgb, var(--primitive-warm-glow) 42%, transparent)); } +.gate-number-chip { padding: 1px 8px; border: 1px solid color-mix(in srgb, var(--primitive-warm-glow) 44%, transparent); border-radius: 7px; color: var(--accent-light); font-weight: 600; letter-spacing: .03em; } +.gate-back { margin-top: 16px; border: 0; background: none; color: var(--content-faint); font-size: 12.5px; letter-spacing: .08em; cursor: pointer; transition: color .25s ease; } +.gate-back:hover { color: var(--accent-light); } +.gate-message { margin-top: 14px; color: var(--content-muted); font-size: 13px; } +.gate-welcome { position: relative; z-index: 2; text-align: center; animation: gate-welcome-in 1s cubic-bezier(.3, .7, .35, 1) both; } +.gate-welcome-kicker { display: inline-block; padding: 6px 16px; border: 1px solid color-mix(in srgb, var(--primitive-warm-glow) 40%, transparent); border-radius: 999px; color: var(--accent-light); font-size: 12px; letter-spacing: .26em; text-indent: .26em; background: color-mix(in srgb, var(--primitive-warm-glow) 8%, transparent); } +.gate-welcome h1 { margin: 20px 0 12px; color: var(--content-primary); font-size: clamp(30px, 4.6vw, 44px); font-weight: 650; letter-spacing: .06em; text-shadow: 0 0 46px color-mix(in srgb, var(--primitive-warm-glow) 34%, transparent); } +.gate-welcome p { color: var(--content-muted); font-size: 14.5px; line-height: 1.7; } +@keyframes gate-welcome-in { 0% { opacity: 0; transform: translateY(26px) scale(.97); } 100% { opacity: 1; transform: translateY(0) scale(1); } } From 2d20170ac7012148105650772b558ca1c11b527f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Sun, 16 Aug 2026 01:25:09 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=E5=A4=A7=E9=97=A8v2=E5=94=AF?= =?UTF-8?q?=E7=BE=8E=E9=87=8D=E7=BB=98=E2=80=94=E2=80=94=E4=B8=80=E7=89=87?= =?UTF-8?q?=E6=B9=96+=E4=BA=94=E5=9D=97=E6=B2=89=E5=BA=95=E7=8E=89(?= =?UTF-8?q?=E5=8F=AA=E9=9C=B2=E5=B0=96=E5=BE=AE=E5=8A=A8)+=E6=B9=96?= =?UTF-8?q?=E9=9D=A2=E6=AD=A3=E4=B8=AD=E6=82=AC=E6=B5=AE=E7=BC=96=E5=8F=B7?= =?UTF-8?q?=E8=88=B1=EF=BC=9B=E7=BC=96=E5=8F=B7=E8=87=AA=E5=8A=A8=E8=A1=A5?= =?UTF-8?q?=E6=9D=A0(ICE/GLS=E7=99=BB=E8=AE=B0=E6=96=87=E6=B3=95)+?= =?UTF-8?q?=E2=88=9E=E5=BC=80=E5=85=B3=E7=82=B9=E4=BA=AE=EF=BC=9B=E8=BF=87?= =?UTF-8?q?=E5=85=B3=E7=8E=89=E5=8D=87=E8=B5=B7=E2=86=92=E6=AC=A2=E8=BF=8E?= =?UTF-8?q?=E8=AF=AD=E2=86=92=E9=92=A5=E5=8C=99=E9=97=A8=EF=BC=9B=E6=96=87?= =?UTF-8?q?=E5=AD=97=E5=A4=A7=E5=88=A0=E6=94=B9=E7=94=BB=E9=9D=A2=E5=8F=99?= =?UTF-8?q?=E4=BA=8B=EF=BC=88=E5=86=B0=E6=9C=9420260816=E8=B0=95=EF=BC=9A?= =?UTF-8?q?=E5=AF=B9=E5=A4=96=E5=88=86=E5=8F=91=E7=9A=84=E8=BD=AF=E4=BB=B6?= =?UTF-8?q?=E8=A6=81=E5=8A=A8=E6=80=81=E7=94=BB=E9=9D=A2=E4=BB=A3=E6=9B=BF?= =?UTF-8?q?=E6=96=87=E5=AD=97=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hololake-native-desktop/src/main.tsx | 112 +++++++++++------- .../hololake-native-desktop/src/styles.css | 62 ++++++---- 2 files changed, 111 insertions(+), 63 deletions(-) diff --git a/product-source/hololake-native-desktop/src/main.tsx b/product-source/hololake-native-desktop/src/main.tsx index bf9e99dd5..ce0383a0e 100644 --- a/product-source/hololake-native-desktop/src/main.tsx +++ b/product-source/hololake-native-desktop/src/main.tsx @@ -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('zero_point_bind', { input: { number: gateNumber.trim() } }) + await invoke('zero_point_bind', { input: { number: gateNumber } }) const snapshot = await invoke('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
-
- +
+