feat(hololake): add domain-routed language membrane and user PNCC

This commit is contained in:
冰朔 2026-08-16 15:10:31 +08:00
commit d563e6fd73
34 changed files with 1949 additions and 144 deletions

View file

@ -54,8 +54,28 @@ interface HomeStatus {
mcpRole: string
}
interface PersonalChannelIdentity { humanSubjectId: string; displayName: string; channelId: string; createdAtUnixMs: number }
interface LoginSession { username: string; host: string; signedInAtUnixMs: number }
interface LoginReceipt { username: string; email: string; host: string }
interface LoginSession { username: string; host: string; domain: string; signedInAtUnixMs: number }
interface LoginReceipt { username: string; email: string; host: string; domain: string }
interface UserPnccChannelSnapshot {
state: string
repositoryId: string
channelId: string
userNumber: string
domain: string
accountUsername: string
accountHost: string
localPath: string
branch: string
gitHead: string
repositoryClean: boolean
createdAtUnixMs: number
gitEngine: string
humanProjection: string
forgejoAdapterState: string
remoteRepositoryUrl?: string
personaBindingClaimed: boolean
authority: string
}
interface PersonalChannelEvent { sequence: number; eventId: string; kind: string; summary: string; occurredAtUnixMs: number; receiptId: string; receiptHash: string }
interface PersonalChannelSnapshot { state: string; identity?: PersonalChannelIdentity; recentEvents: PersonalChannelEvent[]; integrity: { state: string; eventCount: number; receiptCount: number } }
interface KnowledgeDocumentSummary {
@ -109,6 +129,18 @@ interface CodeFileProjection { channelId: string; path: string; format: string;
interface ReceiptEvent { sequence: number; kind: string; observedAtUnixMs: number; eventHash: string }
interface ReceiptProjection { events: ReceiptEvent[] }
interface DiscoveryTicketReceipt { laneId: string; clientInstanceId: string; discoveryTicket: string }
interface NearbyAiDiscoverySnapshot {
state: string
serviceName: string
transport: string
languageProtocol: string
automaticSameDeviceDiscovery: boolean
largeInvitationCopyRequired: boolean
genericAiVisitor: string
guanghuPersona: string
localNetworkDiscovery: string
authority: string
}
interface ReleaseCandidate { candidateId: string; confirmationToken: string; currentVersion: string; version: string; notes: string; features: string[]; fixes: string[]; dataMigrationRequired: boolean }
interface ReleaseCheckReceipt { candidate?: ReleaseCandidate }
interface ZeroPointSnapshot {
@ -134,6 +166,10 @@ function domainDisplayName(domain: string): string {
return names[domain] || '已登记光湖域'
}
function domainLightIndex(domain: string): number {
return ({ MAIN_DOMAIN: 1, BRANCH_DOMAIN: 2, ZERO_DOMAIN: 3, ZERO_SENSE_DOMAIN: 4, FIFTH_DOMAIN: 5 } as Record<string, number>)[domain] || 0
}
function protocolOriginDisplayName(origin: string): string {
return origin.startsWith('FACTORY_DEFAULT') ? '内置默认协议(签名发布通道尚未配置)' : origin
}
@ -165,7 +201,7 @@ const previewCode: CodeChannelSnapshot = { state: 'UNAVAILABLE', channels: [], a
const themes: Array<{ id: ThemeId; name: string }> = [
{ id: 'night', name: '夜湖星光' }, { id: 'dawn', name: '晨湖曦光' }, { id: 'nebula', name: '星云紫夜' }, { id: 'candle', name: '烛畔暖湖' }, { id: 'clear', name: '清浅澄湖' },
]
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', code: '代码频道', receipts: '运行回执', system: '系统详情' }
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
function getOrCreateLocalId(key: string, prefix: string) {
const existing = window.localStorage.getItem(key)
@ -183,6 +219,10 @@ function humanError(error: unknown, context: 'knowledge' | 'code' | 'identity' |
if (value.includes('LOGIN_KEYCHAIN_FAILED')) return '本机钥匙串写入失败,凭证未能安全保存。'
if (value.includes('LOGIN_SESSION_CORRUPT')) return '登录会话已损坏,请重新登录。'
if (value.includes('LOGIN_VERIFICATION_FAILED')) return '仓库验证未通过,请稍后再试。'
if (value.includes('DOMAIN_LOGIN_NOT_PROVISIONED')) return '该域的企业服务器尚未接入,当前不能继续登录。'
if (value.includes('DOMAIN_ROUTE_REQUIRED') || value.includes('ZP_DOMAIN_ROUTE_REQUIRED')) return '编号尚未解析到已登记域,不能开始登录。'
if (value.includes('USER_PNCC_TRUSTED_SUBJECT_REQUIRED')) return '建立人格代码频道前,需要有效的用户编号与仓库账号登录。'
if (value.includes('USER_PNCC_BINDING_MISMATCH')) return '本机频道绑定记录与当前账号不一致,系统已停止加载。'
if (value.includes('HOST_NOT_TRUSTED')) return '该地址不属于已登记的光湖代码频道。'
if (value.includes('URL_INVALID')) return '频道地址无效,请粘贴完整的 HTTPS 克隆地址。'
if (value.includes('DESTINATION_EXISTS')) return '该代码频道已经存在,无需再次克隆。'
@ -300,9 +340,13 @@ function HoloLakeApp() {
const [identityMessage, setIdentityMessage] = useState('')
const [receipts, setReceipts] = useState<ReceiptEvent[]>([])
const [ticket, setTicket] = useState<DiscoveryTicketReceipt | null>(null)
const [nearbyDiscovery, setNearbyDiscovery] = useState<NearbyAiDiscoverySnapshot | null>(null)
const [systemMessage, setSystemMessage] = useState('')
const [releaseCandidate, setReleaseCandidate] = useState<ReleaseCandidate | null>(null)
const [repoLogin, setRepoLogin] = useState<LoginSession | null>(null)
const [userPncc, setUserPncc] = useState<UserPnccChannelSnapshot | null>(null)
const [userPnccBusy, setUserPnccBusy] = useState(false)
const [userPnccMessage, setUserPnccMessage] = useState('')
const [loginUsername, setLoginUsername] = useState('')
const [loginPassword, setLoginPassword] = useState('')
const [loginBusy, setLoginBusy] = useState(false)
@ -355,6 +399,11 @@ function HoloLakeApp() {
const timer = window.setInterval(() => void refreshCore(), 3500)
return () => window.clearInterval(timer)
}, [refreshCore])
useEffect(() => {
invoke<NearbyAiDiscoverySnapshot>('get_nearby_ai_discovery')
.then(setNearbyDiscovery)
.catch(() => setNearbyDiscovery(null))
}, [])
const refreshServerPncc = useCallback(async () => {
setServerPnccBusy(true)
try {
@ -387,6 +436,26 @@ function HoloLakeApp() {
const timer = window.setInterval(() => void loadZeroPoint(), 10000)
return () => window.clearInterval(timer)
}, [loadZeroPoint])
// 已验证编号与已认证账号同时存在时,后端幂等建立或恢复该用户自己的 GH-PNCC。
useEffect(() => {
if (!repoLogin || zeroPoint?.route !== 'verified') {
setUserPncc(null)
return
}
let active = true
setUserPnccBusy(true)
setUserPnccMessage('')
invoke<UserPnccChannelSnapshot>('ensure_user_pncc_channel')
.then((snapshot) => {
if (!active) return
setUserPncc(snapshot)
setUserPnccMessage('本机原生代码频道已就绪。')
void refreshCore()
})
.catch((error) => { if (active) setUserPnccMessage(humanError(error, 'code')) })
.finally(() => { if (active) setUserPnccBusy(false) })
return () => { active = false }
}, [repoLogin, zeroPoint?.route, zeroPoint?.userNumber, refreshCore])
// 本机存在已绑定编号时自动回填,减少重复输入。
useEffect(() => {
if (gateStage !== 'number' || gateRaw) return
@ -649,7 +718,7 @@ function HoloLakeApp() {
// 五湖开场:校验通过=第五域从湖面浮起,镜头沉入湖中再进场。
setLoginRising(true)
await new Promise((resolve) => setTimeout(resolve, 1300))
setRepoLogin({ username: receipt.username, host: receipt.host, signedInAtUnixMs: Date.now() })
setRepoLogin({ username: receipt.username, host: receipt.host, domain: receipt.domain, signedInAtUnixMs: Date.now() })
setLoginRising(false)
setLoginPassword('')
} catch (error) { setLoginMessage(humanError(error, 'login')) }
@ -825,13 +894,19 @@ function HoloLakeApp() {
const renderCode = () => (
<section className="full-workbench code-workbench">
<aside className="code-channels">
<header><div><span className="kicker">CODE CHANNEL</span><h1></h1></div></header>
<header><div><span className="kicker">GH-PNCC</span><h1></h1></div></header>
<section className="pncc-native-card">
<div className="pncc-native-card-title"><span className={userPncc ? 'identity-dot ready' : 'identity-dot'}/><div><b>{userPncc ? `${userPncc.accountUsername} · GH-PNCC` : '正在建立用户频道'}</b><small>{userPnccBusy ? '正在初始化本机 Git…' : userPncc ? `${userPncc.branch} · ${userPncc.gitHead.slice(0, 8)}` : '等待可信绑定'}</small></div></div>
{userPncc ? <dl><div><dt></dt><dd>{domainDisplayName(userPncc.domain)}</dd></div><div><dt></dt><dd>{userPncc.userNumber}</dd></div><div><dt></dt><dd>Git</dd></div><div><dt></dt><dd>HoloLake </dd></div><div><dt>Forgejo </dt><dd> · </dd></div></dl> : <p>{userPnccMessage || '系统先按编号确定所属域,再通过该域的账号与节点入口建立频道。'}</p>}
{userPncc && <button type="button" onClick={() => { const channel = codeChannels.channels.find((item) => item.channelId === userPncc.channelId); if (channel) void browseChannel(channel) }}></button>}
</section>
<div className="channel-section-label"><b></b><small> Git</small></div>
<form className="clone-form" onSubmit={(event) => void cloneCodeChannel(event)}>
<label htmlFor="clone-url"></label><div><input id="clone-url" type="url" value={cloneUrl} placeholder="https://guanghulab.com/code/…" onChange={(event) => setCloneUrl(event.target.value)}/><button disabled={!cloneUrl.trim() || codeBusy}></button></div>
</form>
<button className="local-folder-button" type="button" onClick={() => void selectLocalCodeChannel()}><Icon name="folder"/> Git </button>
<div className="channel-selector">{codeChannels.channels.map((channel) => <button className={activeChannel?.channelId === channel.channelId ? 'active' : ''} key={channel.channelId} type="button" onClick={() => void browseChannel(channel)}><Icon name="code"/><span><b>{channel.name}</b><small>{channel.branch} · {channel.gitHead.slice(0, 8)}</small></span></button>)}</div>
<footer>{codeMessage || '仅提供本机读取,不授予推送、发布或部署权限。'}</footer>
<footer>{codeMessage || userPnccMessage || 'Git 负责耐久化Forgejo 仅作为远端协作适配器。'}</footer>
</aside>
<aside className="repository-tree">
<header><button className="icon-button" disabled={!codeTree?.path} type="button" onClick={() => activeChannel && void browseChannel(activeChannel, codeParent())}><Icon name="back"/></button><div><b>{activeChannel?.name || '尚未选择频道'}</b><small>/{codeTree?.path || ''}</small></div></header>
@ -841,7 +916,7 @@ function HoloLakeApp() {
{activeCodeFile ? <>
<header><div><span>{activeCodeFile.path}</span><small>{activeCodeFile.format.toUpperCase()} · {activeCodeFile.sizeBytes.toLocaleString()} bytes</small></div><div className="mode-switch"><button className={codeMode === 'human' ? 'active' : ''} type="button" onClick={() => setCodeMode('human')}></button><button className={codeMode === 'source' ? 'active' : ''} type="button" onClick={() => setCodeMode('source')}></button></div></header>
<div className="code-reader-scroll">{codeMode === 'human' ? <MarkdownDocument body={activeCodeFile.humanMarkdown}/> : <pre className="source-code"><code>{activeCodeFile.source}</code></pre>}</div>
</> : <div className="workbench-empty"><span>&lt;/&gt;</span><h2></h2><p> MarkdownJSONHDLPYAMLTOML </p></div>}
</> : <div className="workbench-empty"><span>&lt;/&gt;</span><h2>GH-PNCC · </h2><p> HoloLake Git </p></div>}
</main>
</section>
)
@ -861,9 +936,10 @@ function HoloLakeApp() {
<header className="page-title"><div><span className="kicker">SYSTEM EVIDENCE</span><h1></h1><p></p></div></header>
<div className="system-grid">
<section className="plain-panel connection-panel">
<header><div><h2> AI </h2><p>MCP </p></div><span className={connectionOnline ? 'status-chip online' : 'status-chip'}>{connectionOnline ? `${status.directConnectionCount} 个客户端在线` : '当前无客户端'}</span></header>
<dl className="evidence-list"><div><dt></dt><dd>{status.directLocalBrokerState}</dd></div><div><dt></dt><dd>{status.directConnectionCount}</dd></div><div><dt></dt><dd>{status.resumableSessionCount}</dd></div><div><dt></dt><dd>UNIX_STREAM_JSON_LINES</dd></div><div><dt>MCP </dt><dd> / / </dd></div></dl>
{ticket ? <><button className="secondary-button" type="button" onClick={() => void copyInvitation()}><Icon name="copy"/></button><pre className="invitation-data">{invitationText}</pre></> : <button className="primary-button" type="button" disabled={systemBusy} onClick={() => void issueInvitation()}></button>}
<header><div><h2></h2><p> AI HoloLakeMCP </p></div><span className={nearbyDiscovery?.automaticSameDeviceDiscovery ? 'status-chip online' : 'status-chip'}>{nearbyDiscovery?.automaticSameDeviceDiscovery ? '本机自动发现已开启' : '自动发现不可用'}</span></header>
<dl className="evidence-list"><div><dt></dt><dd>GLP/1.0 · </dd></div><div><dt> AI 访</dt><dd>{nearbyDiscovery?.genericAiVisitor === 'EXPRESSION_ONLY_READY' ? '可连接 · 仅语言表达' : '不可用'}</dd></div><div><dt></dt><dd>{nearbyDiscovery?.guanghuPersona === 'BINDING_EVIDENCE_REQUIRED' ? '等待人格绑定证据' : '可连接'}</dd></div><div><dt></dt><dd>{status.directConnectionCount}</dd></div><div><dt></dt><dd>{nearbyDiscovery?.localNetworkDiscovery === 'DEFERRED_UNTIL_ENCRYPTED_TRANSPORT_AND_APPROVAL' ? '等待加密传输与确认闭环' : '已开启'}</dd></div><div><dt>MCP</dt><dd> / </dd></div></dl>
<p className="boundary-note">访</p>
{ticket ? <><button className="secondary-button" type="button" onClick={() => void copyInvitation()}><Icon name="copy"/></button><pre className="invitation-data">{invitationText}</pre></> : <button className="secondary-button" type="button" disabled={systemBusy} onClick={() => void issueInvitation()}></button>}
</section>
<section className="plain-panel">
<header><div><h2> · GH-PNCC</h2><p></p></div><span className={serverPnccReadback === 'LIVE' ? 'status-chip online' : 'status-chip'}>{serverPnccReadback === 'LIVE' ? '实时回读正常' : serverPnccReadback === 'UNAVAILABLE' ? '当前不可回读' : '正在读取'}</span></header>
@ -907,13 +983,20 @@ function HoloLakeApp() {
return <div className="app-shell">
<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>
<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 === domainLightIndex(zeroPoint?.resolvedDomain || '') ? ' 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 && gateStage === 'number' && <section className="gate-domain-map" aria-label="光湖五域公开入口">
<article><i/><b></b><small> · </small></article>
<article><i/><b></b><small> · </small></article>
<article><i/><b></b><small> · </small></article>
<article><i/><b></b><small> · </small></article>
<article className="fifth"><i/><b> · </b><small> · </small></article>
</section>}
{gateRising ? (
<section className="gate-rise" role="status">
<span className="gate-rise-star" aria-hidden="true"><i/></span>
@ -924,8 +1007,8 @@ function HoloLakeApp() {
<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>
<b className="sealed-title"></b>
<small className="sealed-sub"></small>
</div>
<div className="pod-face pod-form">
<div className="gate-pod-row">
@ -953,7 +1036,7 @@ function HoloLakeApp() {
return <div className={`app-shell${sidebarCollapsed ? ' sidebar-collapsed' : ''}${inspectorOpen ? '' : ' inspector-closed'}`}>
<aside className="sidebar">
<div className="brand"><span className="brand-mark brand-lamp" title={sidebarCollapsed ? '展开导航' : '光湖 · HoloLake'} onClick={sidebarCollapsed ? toggleSidebar : undefined}><i className="lamp-glow"/><i className="lamp-core"/><i className="lake-shimmer"/></span><div className="brand-text"><b>HoloLake</b><small className="brand-sub"> · </small><small>GH-AIOS · V0.3.0</small></div><button className="icon-button sidebar-toggle" type="button" title={sidebarCollapsed ? '展开导航' : '收起导航'} onClick={toggleSidebar}><Icon name="chevron"/></button></div>
<div className="brand"><span className="brand-mark brand-lamp" title={sidebarCollapsed ? '展开导航' : '光湖 · HoloLake'} onClick={sidebarCollapsed ? toggleSidebar : undefined}><i className="lamp-glow"/><i className="lamp-core"/><i className="lake-shimmer"/></span><div className="brand-text"><b>HoloLake</b><small className="brand-sub"> · </small><small>GH-AIOS · V0.4.0</small></div><button className="icon-button sidebar-toggle" type="button" title={sidebarCollapsed ? '展开导航' : '收起导航'} onClick={toggleSidebar}><Icon name="chevron"/></button></div>
<nav>{(Object.keys(viewLabels) as ViewId[]).map((item) => <button className={view === item ? 'active' : ''} key={item} type="button" onClick={() => setView(item)}><Icon name={item}/><span>{viewLabels[item]}</span>{item === 'knowledge' && <em>{knowledge.uniqueDocumentCount}</em>}</button>)}</nav>
<div className="sidebar-foot"><span className="identity-dot"/><div><b>{personal.identity?.displayName || '本机空间'}</b><small>{repoLogin ? `${repoLogin.username}@${repoLogin.host}` : (personal.identity ? '个人频道已建立' : '等待首次设置')}</small></div><button className="sign-out" type="button" title="退出代码仓库登录" onClick={() => void signOutRepo()}>退</button></div>
</aside>

View file

@ -276,6 +276,23 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.empty-state { min-height: 160px; padding: 24px; font-size: 13px; }
.code-workbench { grid-template-columns: 280px 290px minmax(0, 1fr); }
.pncc-native-card { margin: 14px 12px 4px; padding: 14px; border: 1px solid color-mix(in srgb, var(--state-ready) 28%, var(--panel-edge)); border-radius: 14px; background: color-mix(in srgb, var(--surface-panel) 90%, var(--state-ready) 10%); }
.pncc-native-card-title { display: flex; align-items: center; gap: 10px; }
.pncc-native-card-title > div { min-width: 0; }
.pncc-native-card-title b, .pncc-native-card-title small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pncc-native-card-title b { color: var(--content-primary); font-size: 13px; }
.pncc-native-card-title small { margin-top: 3px; color: var(--content-muted); font-size: 11px; }
.pncc-native-card .identity-dot.ready { background: var(--state-ready); box-shadow: 0 0 12px color-mix(in srgb, var(--state-ready) 58%, transparent); }
.pncc-native-card dl { margin: 13px 0 11px; display: grid; gap: 6px; }
.pncc-native-card dl div { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 8px; font-size: 10.5px; }
.pncc-native-card dt { color: var(--content-muted); }
.pncc-native-card dd { margin: 0; color: var(--content-secondary); overflow-wrap: anywhere; }
.pncc-native-card p { margin: 12px 0 0; color: var(--content-muted); font-size: 11px; line-height: 1.6; }
.pncc-native-card button { width: 100%; min-height: 34px; border: 1px solid var(--panel-edge); border-radius: 9px; color: var(--content-primary); background: var(--primitive-glass); font: inherit; font-size: 12px; cursor: pointer; }
.channel-section-label { padding: 15px 16px 7px; }
.channel-section-label b, .channel-section-label small { display: block; }
.channel-section-label b { color: var(--content-primary); font-size: 12px; }
.channel-section-label small { margin-top: 3px; color: var(--content-muted); font-size: 10.5px; }
.clone-form { padding: 9px 14px 13px; }
.clone-form label { display: block; margin-bottom: 8px; color: var(--content-muted); font-size: 12px; }
.clone-form > div { display: grid; grid-template-columns: minmax(0, 1fr) auto; }
@ -484,6 +501,16 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.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-domain-map { position: relative; z-index: 2; width: min(1040px, 92vw); display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
.gate-domain-map article { min-width: 0; padding: 12px 13px; display: grid; grid-template-columns: 9px minmax(0, 1fr); column-gap: 8px; border: 1px solid color-mix(in srgb, var(--accent-light) 14%, transparent); border-radius: 13px; background: color-mix(in srgb, var(--panel-bg) 40%, transparent); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); }
.gate-domain-map i { width: 7px; height: 7px; margin-top: 4px; border-radius: 50%; background: color-mix(in srgb, var(--primitive-cool-glow) 70%, transparent); box-shadow: 0 0 12px color-mix(in srgb, var(--primitive-cool-glow) 34%, transparent); }
.gate-domain-map b, .gate-domain-map small { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gate-domain-map b { color: var(--content-secondary); font-size: 11.5px; font-weight: 620; }
.gate-domain-map small { grid-column: 2; margin-top: 4px; color: var(--content-faint); font-size: 9.5px; }
.gate-domain-map article.fifth { border-color: color-mix(in srgb, var(--primitive-warm-glow) 25%, transparent); }
.gate-domain-map article.fifth i { background: var(--primitive-warm-glow); box-shadow: 0 0 16px color-mix(in srgb, var(--primitive-warm-glow) 54%, transparent); }
@media (max-width: 900px) { .gate-domain-map { grid-template-columns: repeat(3, minmax(0, 1fr)); } .gate-domain-map article:nth-child(4), .gate-domain-map article:nth-child(5) { grid-column: span 1; } }
@media (max-height: 700px) { .gate-lake { gap: 16px; } .gate-domain-map article { padding: 8px 10px; } .gate-hero { gap: 5px; } .gate-emblem { width: 48px; height: 48px; } .gate-product { font-size: 34px; } }
.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); }