feat: close complete numbering and responsive fifth-domain UI

This commit is contained in:
冰朔 2026-08-19 13:16:32 +08:00
commit e8b3082ed5
42 changed files with 3243 additions and 417 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

@ -25,13 +25,15 @@
"unavailable_behavior": "BEIJING_TIME_ONLY_NO_FAKE_WEATHER" "unavailable_behavior": "BEIJING_TIME_ONLY_NO_FAKE_WEATHER"
}, },
"visual_lock": { "visual_lock": {
"layout": "QODER_LOCKED_STARLAKE_AND_TRADITIONAL_SURFACES_RESPONSIVELY_ADAPTED", "layout": "QODER_VISUAL_LANGUAGE_SEMANTICALLY_REASSEMBLED_ON_RESPONSIVE_HOLOLAKE_SHELL",
"language_world_themes": ["夜湖星光", "晨湖曦光", "星云紫夜", "烛畔暖湖", "清浅澄湖"], "language_world_themes": ["夜湖星光", "晨湖曦光", "星云紫夜", "烛畔暖湖", "清浅澄湖"],
"traditional_finishes": ["曜夜", "星辉", "深海", "翡翠", "朱砂", "香槟", "瓷光", "雪霁"], "traditional_finishes": ["曜夜", "星辉", "深海", "翡翠", "朱砂", "香槟", "瓷光", "雪霁"],
"all_downstream_pages_inherit_active_surface_tokens": true, "all_downstream_pages_inherit_active_surface_tokens": true,
"ambient_motion_requires_recent_human_pointer_or_keyboard_activity": true, "ambient_motion_requires_recent_human_pointer_or_keyboard_activity": true,
"idle_visual_state": "STATIC", "idle_visual_state": "STATIC",
"responsive_layout": "RESIZE_OBSERVER_PLUS_SEMANTIC_TOKEN_BALANCE", "responsive_layout": "CONTAINER_MEASURED_PANORAMIC_WIDE_COMPACT_STACKED_REFLOW",
"domain_semantic_order_is_stable": true,
"private_channel_separates_native_organs_from_installed_modules": true,
"climate_changes_routing_permission_or_fact": false, "climate_changes_routing_permission_or_fact": false,
"daily_sampling_domain_changes_open_domain": false, "daily_sampling_domain_changes_open_domain": false,
"real_city_exposed_in_ui": false, "real_city_exposed_in_ui": false,

View file

@ -1,6 +1,6 @@
{ {
"schema": "hololake.gls-executable-projections/v2", "schema": "hololake.gls-executable-projections/v2",
"source_commit": "d5b1111fcaccaccf025070e531631f2b3cbb00cd", "source_commit": "104a5d73162bdf4a529701e65898e2bc2863ea9e",
"runtime_graph_rule": "ONLY_EXPLICIT_RUNTIME_REQUIRES_EDGES_ENTER_ACTIVATION_GRAPH", "runtime_graph_rule": "ONLY_EXPLICIT_RUNTIME_REQUIRES_EDGES_ENTER_ACTIVATION_GRAPH",
"projections": { "projections": {
"GLS-0250": {"stage":"P0","projection_kind":"TYPED_FACT_AND_DOMAIN_BOUNDARY","adapter":"origin-domain-topology","event_kinds":["BOOTSTRAP","DOMAIN_ROUTE"],"dependencies":[]}, "GLS-0250": {"stage":"P0","projection_kind":"TYPED_FACT_AND_DOMAIN_BOUNDARY","adapter":"origin-domain-topology","event_kinds":["BOOTSTRAP","DOMAIN_ROUTE"],"dependencies":[]},

View file

@ -1,7 +1,7 @@
{ {
"schema": "hololake.gls-native-runtime-kernel/v1", "schema": "hololake.gls-native-runtime-kernel/v1",
"record_id": "HLP-GLS-NATIVE-RUNTIME-KERNEL-001", "record_id": "HLP-GLS-NATIVE-RUNTIME-KERNEL-001",
"source_commit": "d5b1111fcaccaccf025070e531631f2b3cbb00cd", "source_commit": "104a5d73162bdf4a529701e65898e2bc2863ea9e",
"receipt_schema": "hololake.protocol-decision-receipt/v1", "receipt_schema": "hololake.protocol-decision-receipt/v1",
"decision_set": ["ALLOW", "DENY", "AMBIGUOUS", "UNVERIFIED"], "decision_set": ["ALLOW", "DENY", "AMBIGUOUS", "UNVERIFIED"],
"runtime_boundaries": { "runtime_boundaries": {

View file

@ -0,0 +1,33 @@
{
"schema": "hololake.gls-numbered-reference-nodes/v1",
"record_id": "HLP-GLS-NUMBERED-REFERENCE-REGISTRY-001",
"source": {
"repository": "REPO-012",
"commit": "104a5d73162bdf4a529701e65898e2bc2863ea9e"
},
"policy": {
"number_is_coordinate_not_authority": true,
"independent_protocol_source_required_for_execution": true,
"reference_only_nodes_may_execute": false,
"unknown_reference": "FAIL_CLOSED",
"unresolved_number_reference_allowed": false
},
"nodes": [
{"protocol_id":"GLS-0010","node_number":"HLP-GLS-REF-0010","title":"Guanghu Protocol Registry Center Standard","reference_kind":"NORMATIVE_REFERENCE","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0140","node_number":"HLP-GLS-REF-0140","title":"Context Loading Specification","reference_kind":"NORMATIVE_REFERENCE","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0401","node_number":"HLP-GLS-REF-0401","title":"Tree","reference_kind":"SCHEMA_IMPORT","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0402","node_number":"HLP-GLS-REF-0402","title":"Leaf","reference_kind":"SCHEMA_IMPORT","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0403","node_number":"HLP-GLS-REF-0403","title":"Lock","reference_kind":"SCHEMA_IMPORT","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0404","node_number":"HLP-GLS-REF-0404","title":"Trigger","reference_kind":"SCHEMA_IMPORT","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0406","node_number":"HLP-GLS-REF-0406","title":"Evidence","reference_kind":"SCHEMA_IMPORT","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0407","node_number":"HLP-GLS-REF-0407","title":"Correction","reference_kind":"SCHEMA_IMPORT","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0409","node_number":"HLP-GLS-REF-0409","title":"Machine-State History","reference_kind":"SCHEMA_IMPORT","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0602","node_number":"HLP-GLS-REF-0602","title":"Authorization","reference_kind":"SCHEMA_IMPORT","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0603","node_number":"HLP-GLS-REF-0603","title":"Signature","reference_kind":"SCHEMA_IMPORT","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0604","node_number":"HLP-GLS-REF-0604","title":"Integrity","reference_kind":"EVIDENCE_ONLY","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0605","node_number":"HLP-GLS-REF-0605","title":"Semantic Safety Boundary","reference_kind":"SCHEMA_IMPORT","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0814","node_number":"HLP-GLS-REF-0814","title":"Tool Runtime","reference_kind":"NORMATIVE_REFERENCE","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0816","node_number":"HLP-GLS-REF-0816","title":"Checkpoint Runtime","reference_kind":"NORMATIVE_REFERENCE","source_state":"ROADMAP_REFERENCE_ONLY"},
{"protocol_id":"GLS-0830","node_number":"HLP-GLS-REF-0830","title":"Guanghu Language World Core","reference_kind":"NORMATIVE_REFERENCE","source_state":"ROADMAP_REFERENCE_ONLY"}
]
}

View file

@ -51,7 +51,7 @@
], ],
"trust_registry": { "trust_registry": {
"repository": "REPO-012", "repository": "REPO-012",
"source_commit": "d5b1111fcaccaccf025070e531631f2b3cbb00cd", "source_commit": "104a5d73162bdf4a529701e65898e2bc2863ea9e",
"source_path": "routing/persona-control-authorization-signers.json", "source_path": "routing/persona-control-authorization-signers.json",
"source_sha256": "35e7ebac46034e9df5cf61d4dccfee2e624331c1daf08251acb07c3d626974ef", "source_sha256": "35e7ebac46034e9df5cf61d4dccfee2e624331c1daf08251acb07c3d626974ef",
"registry_id": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001", "registry_id": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001",

View file

@ -3,7 +3,7 @@
"record_id": "HLP-ZERO-CORE-NUMBERING-KERNEL-001", "record_id": "HLP-ZERO-CORE-NUMBERING-KERNEL-001",
"authority": { "authority": {
"repository": "REPO-012", "repository": "REPO-012",
"source_commit": "d5b1111fcaccaccf025070e531631f2b3cbb00cd", "source_commit": "104a5d73162bdf4a529701e65898e2bc2863ea9e",
"source_path": "routing/guanghu-identity-authority-map.json", "source_path": "routing/guanghu-identity-authority-map.json",
"map_id": "GH-IDENTITY-AUTHORITY-MAP-001", "map_id": "GH-IDENTITY-AUTHORITY-MAP-001",
"map_version": "2026-08-10.1", "map_version": "2026-08-10.1",

View file

@ -12,9 +12,10 @@ const sourceRoot = explicitSourceRoot || (repoRoot ? path.join(repoRoot, 'gls')
const sourceCommit = args.get('--source-commit') const sourceCommit = args.get('--source-commit')
const output = args.get('--output') const output = args.get('--output')
const projectionsPath = args.get('--projections') const projectionsPath = args.get('--projections')
const referencesPath = args.get('--references')
if (!repoRoot || !sourceRoot || !sourceCommit || !output || !projectionsPath) { if (!repoRoot || !sourceRoot || !sourceCommit || !output || !projectionsPath || !referencesPath) {
throw new Error('usage: compile-gls-runtime-registry.mjs --repo-root <REPO-012> --source-commit <sha> --output <manifest.json> --projections <projections.json>') throw new Error('usage: compile-gls-runtime-registry.mjs --repo-root <REPO-012> --source-commit <sha> --output <manifest.json> --projections <projections.json> --references <reference-nodes.json>')
} }
if (!/^[a-f0-9]{40}$/.test(sourceCommit)) throw new Error('source commit must be a full SHA-1') if (!/^[a-f0-9]{40}$/.test(sourceCommit)) throw new Error('source commit must be a full SHA-1')
@ -168,6 +169,18 @@ if (projectionManifest.schema !== 'hololake.gls-executable-projections/v2'
|| projectionManifest.source_commit !== sourceCommit) { || projectionManifest.source_commit !== sourceCommit) {
throw new Error('projection manifest does not match the selected REPO-012 source commit') throw new Error('projection manifest does not match the selected REPO-012 source commit')
} }
const referenceManifest = JSON.parse(await readFile(referencesPath, 'utf8'))
if (referenceManifest.schema !== 'hololake.gls-numbered-reference-nodes/v1'
|| referenceManifest.record_id !== 'HLP-GLS-NUMBERED-REFERENCE-REGISTRY-001'
|| referenceManifest.source?.repository !== 'REPO-012'
|| referenceManifest.source?.commit !== sourceCommit
|| referenceManifest.policy?.number_is_coordinate_not_authority !== true
|| referenceManifest.policy?.independent_protocol_source_required_for_execution !== true
|| referenceManifest.policy?.reference_only_nodes_may_execute !== false
|| referenceManifest.policy?.unknown_reference !== 'FAIL_CLOSED'
|| referenceManifest.policy?.unresolved_number_reference_allowed !== false) {
throw new Error('numbered reference manifest does not match the selected REPO-012 source commit or safety boundary')
}
const protocolRegistryPath = 'gls/GLS-PROTOCOL-REGISTRY.json' const protocolRegistryPath = 'gls/GLS-PROTOCOL-REGISTRY.json'
const glsEntryPath = 'gls/GLS-ENTRY.hdlp' const glsEntryPath = 'gls/GLS-ENTRY.hdlp'
@ -222,29 +235,35 @@ for (const relative of await walk(routingRoot)) {
} }
} }
const candidates = (await walk(sourceRoot)) const allSourceFiles = await walk(sourceRoot)
.map((relative) => ({ relative, match: path.basename(relative).match(/^(GLS-\d{4}).*\.hdlp$/) })) const candidates = allSourceFiles
.map((relative) => ({ relative, match: path.basename(relative).match(/^(GLS-\d{4})(?:[^0-9].*)?\.(hdlp|md)$/) }))
.filter((item) => item.match) .filter((item) => item.match)
.map((item) => ({ id: item.match[1], relative: item.relative })) .map((item) => ({ id: item.match[1], relative: item.relative, source_format: item.match[2] === 'hdlp' ? 'HDLP_PROTOCOL_SOURCE' : 'LEGACY_MARKDOWN_EVIDENCE' }))
const grouped = new Map() const grouped = new Map()
for (const candidate of candidates) { for (const candidate of candidates) {
const group = grouped.get(candidate.id) || [] const group = grouped.get(candidate.id) || []
group.push(candidate.relative) group.push(candidate)
grouped.set(candidate.id, group) grouped.set(candidate.id, group)
} }
const selectedSource = new Map() const selectedSource = new Map()
for (const [id, sources] of grouped) { for (const [id, sources] of grouped) {
sources.sort((left, right) => sourceRank(left) - sourceRank(right) || left.localeCompare(right)) const protocolSources = sources.filter((source) => source.source_format === 'HDLP_PROTOCOL_SOURCE')
const relative = sources[0] if (protocolSources.length === 0) continue
protocolSources.sort((left, right) => sourceRank(left.relative) - sourceRank(right.relative)
|| left.relative.localeCompare(right.relative))
const selected = protocolSources[0]
const relative = selected.relative
selectedSource.set(id, { selectedSource.set(id, {
relative, relative,
raw: await readFile(path.join(sourceRoot, relative), 'utf8'), raw: await readFile(path.join(sourceRoot, relative), 'utf8'),
alternate_source_count: sources.length - 1, source_format: selected.source_format,
alternate_source_count: protocolSources.length - 1,
}) })
} }
const knownSourceIds = new Set(selectedSource.keys()) let knownSourceIds = new Set(selectedSource.keys())
const protocolRegistryIds = new Set(registryMetadata.keys()) const protocolRegistryIds = new Set(registryMetadata.keys())
const draftIds = new Set((protocolRegistry.registered_draft_protocols || []).map((entry) => entry.id)) const draftIds = new Set((protocolRegistry.registered_draft_protocols || []).map((entry) => entry.id))
const legacyDependencyTargets = new Set() const legacyDependencyTargets = new Set()
@ -256,6 +275,56 @@ for (const id of draftIds) {
} }
const legacyDependencyCycles = stronglyConnectedComponents(draftGraph) const legacyDependencyCycles = stronglyConnectedComponents(draftGraph)
// 旧 Notion 导出只在现行协议真实引用且独立 HDLP 正本缺席时作为历史证据节点进入。
// 未被现行图引用的旧页面不得因为文件名像 GLS 编号就自动取得当前注册地位。
for (const id of [...legacyDependencyTargets].filter((target) => !knownSourceIds.has(target))) {
if (!authorityDeclarations.has(id)) continue
const legacySources = (grouped.get(id) || []).filter((source) => source.source_format === 'LEGACY_MARKDOWN_EVIDENCE')
if (legacySources.length === 0) continue
legacySources.sort((left, right) => sourceRank(left.relative) - sourceRank(right.relative) || left.relative.localeCompare(right.relative))
const selected = legacySources[0]
selectedSource.set(id, {
relative: selected.relative,
raw: await readFile(path.join(sourceRoot, selected.relative), 'utf8'),
source_format: selected.source_format,
alternate_source_count: legacySources.length - 1,
})
}
knownSourceIds = new Set(selectedSource.keys())
const missingSourceTargets = [...legacyDependencyTargets].filter((id) => !knownSourceIds.has(id)).sort()
const referenceIds = new Set()
const referenceNodeNumbers = new Set()
const numberedReferenceNodes = []
for (const node of referenceManifest.nodes || []) {
if (!/^GLS-\d{4}$/.test(node.protocol_id)
|| !/^HLP-GLS-REF-\d{4}$/.test(node.node_number)
|| !node.title
|| !['NORMATIVE_REFERENCE', 'SCHEMA_IMPORT', 'EVIDENCE_ONLY'].includes(node.reference_kind)
|| node.source_state !== 'ROADMAP_REFERENCE_ONLY'
|| !referenceIds.add(node.protocol_id)
|| !referenceNodeNumbers.add(node.node_number)) {
throw new Error(`invalid or duplicated numbered reference node: ${node.protocol_id || 'UNKNOWN'}`)
}
const evidencePaths = []
for (const relative of allSourceFiles) {
if (!/\.(?:hdlp|md|json|ya?ml)$/.test(relative)) continue
const raw = await readFile(path.join(sourceRoot, relative), 'utf8')
if (raw.includes(node.protocol_id)) evidencePaths.push(`gls/${relative}`)
}
if (evidencePaths.length === 0) throw new Error(`numbered reference node has no REPO-012 evidence: ${node.protocol_id}`)
numberedReferenceNodes.push({
...node,
execution_state: 'REFERENCE_ONLY_NOT_EXECUTABLE',
evidence_paths: evidencePaths.sort().slice(0, 12),
})
}
if (missingSourceTargets.length !== referenceIds.size
|| missingSourceTargets.some((id) => !referenceIds.has(id))
|| [...referenceIds].some((id) => !missingSourceTargets.includes(id))) {
throw new Error(`numbered reference registry coverage mismatch: missing=${missingSourceTargets.join(',')} registered=${[...referenceIds].sort().join(',')}`)
}
const protocols = [] const protocols = []
for (const [id, source] of [...selectedSource.entries()].sort(([left], [right]) => left.localeCompare(right))) { for (const [id, source] of [...selectedSource.entries()].sort(([left], [right]) => left.localeCompare(right))) {
const heading = source.raw.split(/\r?\n/).find((line) => line.startsWith('#') && line.includes(id)) const heading = source.raw.split(/\r?\n/).find((line) => line.startsWith('#') && line.includes(id))
@ -280,6 +349,8 @@ for (const [id, source] of [...selectedSource.entries()].sort(([left], [right])
enters_runtime_graph: false, enters_runtime_graph: false,
target_registered: protocolRegistryIds.has(target), target_registered: protocolRegistryIds.has(target),
target_numbered_source_available: knownSourceIds.has(target), target_numbered_source_available: knownSourceIds.has(target),
target_number_coordinate_available: knownSourceIds.has(target) || referenceIds.has(target),
target_resolution: knownSourceIds.has(target) ? 'NUMBERED_PROTOCOL_SOURCE' : referenceIds.has(target) ? 'NUMBERED_REFERENCE_NODE' : 'UNRESOLVED',
})), })),
...(projection?.dependencies || []).map((target) => ({ ...(projection?.dependencies || []).map((target) => ({
target, target,
@ -288,18 +359,21 @@ for (const [id, source] of [...selectedSource.entries()].sort(([left], [right])
enters_runtime_graph: true, enters_runtime_graph: true,
target_registered: protocolRegistryIds.has(target), target_registered: protocolRegistryIds.has(target),
target_numbered_source_available: knownSourceIds.has(target), target_numbered_source_available: knownSourceIds.has(target),
target_number_coordinate_available: knownSourceIds.has(target) || referenceIds.has(target),
target_resolution: knownSourceIds.has(target) ? 'NUMBERED_PROTOCOL_SOURCE' : referenceIds.has(target) ? 'NUMBERED_REFERENCE_NODE' : 'UNRESOLVED',
})), })),
] ]
const activationBlockers = [] const activationBlockers = []
if (!projection) activationBlockers.push('NO_EXECUTABLE_ADAPTER') if (!projection) activationBlockers.push('NO_EXECUTABLE_ADAPTER')
if (registrationState === 'DISCOVERED_UNRECONCILED') activationBlockers.push('REGISTRATION_NOT_RECONCILED') if (registrationState === 'DISCOVERED_UNRECONCILED') activationBlockers.push('REGISTRATION_NOT_RECONCILED')
if (authorityConflict) activationBlockers.push('REGISTRATION_SOURCE_CONFLICT') if (authorityConflict) activationBlockers.push('REGISTRATION_SOURCE_CONFLICT')
if (legacyDependencies.some((target) => !knownSourceIds.has(target)) && !projection) activationBlockers.push('DEPENDENCY_NUMBERED_SOURCE_MISSING') if (legacyDependencies.some((target) => !knownSourceIds.has(target) && !referenceIds.has(target)) && !projection) activationBlockers.push('DEPENDENCY_NUMBER_COORDINATE_MISSING')
protocols.push({ protocols.push({
id, id,
title: cleanHeading(heading || id, id), title: cleanHeading(heading || id, id),
source_status: sourceStatus(source.raw), source_status: sourceStatus(source.raw),
source_path: `gls/${source.relative}`, source_path: `gls/${source.relative}`,
source_format: source.source_format,
source_sha256: createHash('sha256').update(source.raw).digest('hex'), source_sha256: createHash('sha256').update(source.raw).digest('hex'),
alternate_source_count: source.alternate_source_count, alternate_source_count: source.alternate_source_count,
registration: { registration: {
@ -354,6 +428,7 @@ for (const protocol of protocols) {
} }
const dependenciesNotInRegistry = [...legacyDependencyTargets].filter((id) => !protocolRegistryIds.has(id)).sort() const dependenciesNotInRegistry = [...legacyDependencyTargets].filter((id) => !protocolRegistryIds.has(id)).sort()
const dependenciesWithoutNumberedSource = [...legacyDependencyTargets].filter((id) => !knownSourceIds.has(id)).sort() const dependenciesWithoutNumberedSource = [...legacyDependencyTargets].filter((id) => !knownSourceIds.has(id)).sort()
const unresolvedNumberReferences = dependenciesWithoutNumberedSource.filter((id) => !referenceIds.has(id))
const numberedSourcesNotInProtocolRegistry = [...knownSourceIds].filter((id) => !protocolRegistryIds.has(id)).sort() const numberedSourcesNotInProtocolRegistry = [...knownSourceIds].filter((id) => !protocolRegistryIds.has(id)).sort()
const registry = { const registry = {
schema: 'hololake.gls-runtime-manifest/v2', schema: 'hololake.gls-runtime-manifest/v2',
@ -384,6 +459,10 @@ const registry = {
legacy_dependency_target_count: legacyDependencyTargets.size, legacy_dependency_target_count: legacyDependencyTargets.size,
dependencies_not_in_protocol_registry: dependenciesNotInRegistry, dependencies_not_in_protocol_registry: dependenciesNotInRegistry,
dependencies_without_numbered_source: dependenciesWithoutNumberedSource, dependencies_without_numbered_source: dependenciesWithoutNumberedSource,
numbered_reference_node_count: numberedReferenceNodes.length,
unresolved_number_references: unresolvedNumberReferences,
unresolved_number_reference_count: unresolvedNumberReferences.length,
every_dependency_has_number_coordinate: unresolvedNumberReferences.length === 0,
numbered_sources_not_in_protocol_registry: numberedSourcesNotInProtocolRegistry, numbered_sources_not_in_protocol_registry: numberedSourcesNotInProtocolRegistry,
legacy_dependency_cycles: legacyDependencyCycles, legacy_dependency_cycles: legacyDependencyCycles,
source_reference_cycles: legacyDependencyCycles, source_reference_cycles: legacyDependencyCycles,
@ -395,8 +474,10 @@ const registry = {
protocol_count: protocols.length, protocol_count: protocols.length,
executable_projection_count: executableCount, executable_projection_count: executableCount,
inventoried_not_executable_count: protocols.length - executableCount, inventoried_not_executable_count: protocols.length - executableCount,
number_coordinate_count: protocols.length + numberedReferenceNodes.length,
numbered_reference_nodes: numberedReferenceNodes,
protocols, protocols,
} }
await writeFile(output, `${JSON.stringify(registry, null, 2)}\n`) await writeFile(output, `${JSON.stringify(registry, null, 2)}\n`)
console.log(`GLS_RUNTIME_MANIFEST_COMPILED protocols=${protocols.length} registered=${protocolRegistryIds.size} executable=${executableCount} legacy_cycles=${legacyDependencyCycles.length} output=${output}`) console.log(`GLS_RUNTIME_MANIFEST_COMPILED protocols=${protocols.length} references=${numberedReferenceNodes.length} unresolved=${unresolvedNumberReferences.length} registered=${protocolRegistryIds.size} executable=${executableCount} legacy_cycles=${legacyDependencyCycles.length} output=${output}`)

View file

@ -8,6 +8,7 @@ const paths = {
identity: resolve(root, 'contracts/zero-core-numbering-kernel.json'), identity: resolve(root, 'contracts/zero-core-numbering-kernel.json'),
webview: resolve(root, 'contracts/numbered-ipc-registry.json'), webview: resolve(root, 'contracts/numbered-ipc-registry.json'),
broker: resolve(root, 'contracts/direct-local-broker-numbered-registry.json'), broker: resolve(root, 'contracts/direct-local-broker-numbered-registry.json'),
gls: resolve(root, 'contracts/gls-runtime-registry.json'),
output: resolve(root, 'generated/unified-number-coordinate-tree.json'), output: resolve(root, 'generated/unified-number-coordinate-tree.json'),
} }
@ -23,6 +24,7 @@ export function compileUnifiedNumberTree() {
const identity = readSource(paths.identity) const identity = readSource(paths.identity)
const webview = readSource(paths.webview) const webview = readSource(paths.webview)
const broker = readSource(paths.broker) const broker = readSource(paths.broker)
const gls = readSource(paths.gls)
const routes = [ const routes = [
...webview.value.operations.map((route) => ({ ...webview.value.operations.map((route) => ({
transport: 'TAURI_WEBVIEW_NUMBERED_IPC', transport: 'TAURI_WEBVIEW_NUMBERED_IPC',
@ -64,8 +66,48 @@ export function compileUnifiedNumberTree() {
if (routes.length !== uniquePaths.size || routes.length !== uniqueOperations.size) { if (routes.length !== uniquePaths.size || routes.length !== uniqueOperations.size) {
throw new Error('HOLOLAKE_UNIFIED_NUMBER_TREE_DUPLICATE_COORDINATE') throw new Error('HOLOLAKE_UNIFIED_NUMBER_TREE_DUPLICATE_COORDINATE')
} }
const identityNodes = identity.value.namespaces.map((namespace) => ({
nodeKind: 'IDENTITY_NAMESPACE',
nodeNumber: `HLP-IDENTITY-NS-${namespace.id}`,
namespaceId: namespace.id,
subjectKind: namespace.subject_kind,
domainScope: namespace.domain_scope,
admission: namespace.human_entry ? 'REGISTERED_HUMAN_NAMESPACE' : 'NON_HUMAN_NAMESPACE',
executionState: 'AUTHORITY_RESOLUTION_ONLY',
evidence: identity.value.authority.map_id,
path: `HLP-NUMBER-WORLD-ROOT-001/IDENTITY/${namespace.id}`,
})).sort((left, right) => left.path.localeCompare(right.path, 'en'))
const protocolNodes = [
...gls.value.protocols.map((protocol) => ({
nodeKind: 'GLS_PROTOCOL_SOURCE',
nodeNumber: protocol.id,
protocolId: protocol.id,
title: protocol.title,
sourceState: protocol.source_format,
executionState: protocol.projection_state,
evidence: protocol.source_sha256,
path: `HLP-NUMBER-WORLD-ROOT-001/GLS/SOURCE/${protocol.id}`,
})),
...gls.value.numbered_reference_nodes.map((reference) => ({
nodeKind: 'GLS_REFERENCE_ONLY',
nodeNumber: reference.node_number,
protocolId: reference.protocol_id,
title: reference.title,
sourceState: reference.source_state,
executionState: reference.execution_state,
evidence: reference.evidence_paths[0],
path: `HLP-NUMBER-WORLD-ROOT-001/GLS/REFERENCE/${reference.node_number}`,
})),
].sort((left, right) => left.path.localeCompare(right.path, 'en'))
const allPaths = [...routes.map((route) => route.path), ...identityNodes.map((node) => node.path), ...protocolNodes.map((node) => node.path)]
if (new Set(allPaths).size !== allPaths.length
|| gls.value.reconciliation.unresolved_number_reference_count !== 0
|| gls.value.reconciliation.every_dependency_has_number_coordinate !== true
|| protocolNodes.some((node) => node.nodeKind === 'GLS_REFERENCE_ONLY' && node.executionState !== 'REFERENCE_ONLY_NOT_EXECUTABLE')) {
throw new Error('HOLOLAKE_UNIFIED_NUMBER_TREE_INCOMPLETE_COVERAGE')
}
return { return {
schema: 'hololake.unified-number-coordinate-tree/v1', schema: 'hololake.unified-number-coordinate-tree/v2',
recordId: 'HLP-UNIFIED-NUMBER-TREE-001', recordId: 'HLP-UNIFIED-NUMBER-TREE-001',
state: 'MACHINE_COMPILED_STARTUP_ENFORCED', state: 'MACHINE_COMPILED_STARTUP_ENFORCED',
rootNumber: 'HLP-NUMBER-WORLD-ROOT-001', rootNumber: 'HLP-NUMBER-WORLD-ROOT-001',
@ -84,6 +126,7 @@ export function compileUnifiedNumberTree() {
{ recordId: identity.value.record_id, sha256: identity.sha256 }, { recordId: identity.value.record_id, sha256: identity.sha256 },
{ recordId: webview.value.record_id, sha256: webview.sha256 }, { recordId: webview.value.record_id, sha256: webview.sha256 },
{ recordId: broker.value.record_id, sha256: broker.sha256 }, { recordId: broker.value.record_id, sha256: broker.sha256 },
{ recordId: gls.value.record_id, sha256: gls.sha256 },
], ],
invariants: { invariants: {
numberIsStableCoordinateNotAuthority: true, numberIsStableCoordinateNotAuthority: true,
@ -91,9 +134,18 @@ export function compileUnifiedNumberTree() {
admissionIsSeparateFromIdentity: true, admissionIsSeparateFromIdentity: true,
everyPhysicalCallHasNumberedRoute: true, everyPhysicalCallHasNumberedRoute: true,
everyAcceptedCallHasEvidenceClass: true, everyAcceptedCallHasEvidenceClass: true,
everyProtocolReferenceHasNumberCoordinate: true,
referenceOnlyNodesNeverExecutable: true,
unresolvedNumberReferenceCount: 0,
mismatchedCoordinate: 'FAIL_CLOSED', mismatchedCoordinate: 'FAIL_CLOSED',
}, },
coordinateCount: routes.length + identityNodes.length + protocolNodes.length,
routeCount: routes.length, routeCount: routes.length,
identityNodeCount: identityNodes.length,
protocolNodeCount: protocolNodes.length,
referenceOnlyNodeCount: protocolNodes.filter((node) => node.nodeKind === 'GLS_REFERENCE_ONLY').length,
identityNodes,
protocolNodes,
routes, routes,
} }
} }

View file

@ -14,6 +14,7 @@ const starlakeStyles = read('src/modules/qoder-surface/starlake-surface.css')
const traditional = read('src/modules/qoder-surface/TraditionalSurface.tsx') const traditional = read('src/modules/qoder-surface/TraditionalSurface.tsx')
const traditionalStyles = read('src/modules/qoder-surface/traditional-surface.css') const traditionalStyles = read('src/modules/qoder-surface/traditional-surface.css')
const visualBalance = read('src/modules/qoder-surface/visual-balance.ts') const visualBalance = read('src/modules/qoder-surface/visual-balance.ts')
const layoutProfile = read('src/modules/qoder-surface/layout-profile.ts')
test('dynamic world surface is signed, opt-in and cannot mutate world facts', () => { test('dynamic world surface is signed, opt-in and cannot mutate world facts', () => {
assert.equal(modulePackage.manifest.moduleNumber, 'HLP-MOD-OFFICIAL-DYNAMIC-WORLD-SURFACE-0001') assert.equal(modulePackage.manifest.moduleNumber, 'HLP-MOD-OFFICIAL-DYNAMIC-WORLD-SURFACE-0001')
@ -37,7 +38,7 @@ test('climate query crosses the unique numbered route and fails closed before ac
}) })
test('Qoder locked two-track surface is admitted without simulated product state', () => { test('Qoder locked two-track surface is admitted without simulated product state', () => {
assert.equal(contract.visual_lock.layout, 'QODER_LOCKED_STARLAKE_AND_TRADITIONAL_SURFACES_RESPONSIVELY_ADAPTED') assert.equal(contract.visual_lock.layout, 'QODER_VISUAL_LANGUAGE_SEMANTICALLY_REASSEMBLED_ON_RESPONSIVE_HOLOLAKE_SHELL')
assert.equal(contract.visual_lock.language_world_themes.length, 5) assert.equal(contract.visual_lock.language_world_themes.length, 5)
assert.equal(contract.visual_lock.traditional_finishes.length, 8) assert.equal(contract.visual_lock.traditional_finishes.length, 8)
assert.equal(contract.visual_lock.all_downstream_pages_inherit_active_surface_tokens, true) assert.equal(contract.visual_lock.all_downstream_pages_inherit_active_surface_tokens, true)
@ -45,6 +46,9 @@ test('Qoder locked two-track surface is admitted without simulated product state
assert.match(frontend, /天气源暂不可用;湖面只使用北京时间,不生成假天气。/) assert.match(frontend, /天气源暂不可用;湖面只使用北京时间,不生成假天气。/)
assert.match(styles, /\.world-climate-veil/) assert.match(styles, /\.world-climate-veil/)
assert.match(starlake, /ResizeObserver/) assert.match(starlake, /ResizeObserver/)
assert.match(starlake, /data-layout=\{layout\.profile\}/)
assert.match(layoutProfile, /panoramic.*wide.*compact.*stacked/s)
assert.doesNotMatch(starlake, /naturalScale|sideCenters|onDuty \? 2/)
assert.match(starlakeStyles, /\.starlake-scene\.awake/) assert.match(starlakeStyles, /\.starlake-scene\.awake/)
assert.match(traditional, /broadcasts\.map/) assert.match(traditional, /broadcasts\.map/)
assert.match(traditionalStyles, /\.official-world\.surface-traditional/) assert.match(traditionalStyles, /\.official-world\.surface-traditional/)

View file

@ -3,6 +3,7 @@ import fs from 'node:fs'
import test from 'node:test' import test from 'node:test'
const source = fs.readFileSync(new URL('../src/main.tsx', import.meta.url), 'utf8') const source = fs.readFileSync(new URL('../src/main.tsx', import.meta.url), 'utf8')
const privateChannel = fs.readFileSync(new URL('../src/modules/private-channel/PrivateChannelSurface.tsx', import.meta.url), 'utf8')
test('Zhizhi remains absent from the public Fifth Domain card', () => { test('Zhizhi remains absent from the public Fifth Domain card', () => {
const publicCard = source.slice(source.indexOf("domain: 'FIFTH_DOMAIN'"), source.indexOf('const starPoints')) const publicCard = source.slice(source.indexOf("domain: 'FIFTH_DOMAIN'"), source.indexOf('const starPoints'))
@ -23,7 +24,10 @@ test('the Eternal Lake Heart system body exposes both owned branches before chan
assert.match(source, /title="爱之核心子系统" meta="责任主体 · 之之"/) assert.match(source, /title="爱之核心子系统" meta="责任主体 · 之之"/)
assert.match(source, /title="光之湖" meta="人格体居所"/) assert.match(source, /title="光之湖" meta="人格体居所"/)
assert.match(source, /worldStage === 'heartbeat'/) assert.match(source, /worldStage === 'heartbeat'/)
assert.match(source, /<h1>心跳核心频道<\/h1>/) assert.match(source, /<PrivateChannelSurface ownerName="冰朔" ownerNumber="ICE-GL∞"/)
assert.match(privateChannel, /view === 'home' \? '心跳核心频道'/)
assert.match(privateChannel, /PrivateChannelView = 'home' \| 'native' \| 'modules'/)
assert.match(privateChannel, /private-water-bay/)
}) })
test('knowledge belongs to Heartbeat Core while persona repositories belong to Light Lake', () => { test('knowledge belongs to Heartbeat Core while persona repositories belong to Light Lake', () => {
@ -32,12 +36,22 @@ test('knowledge belongs to Heartbeat Core while persona repositories belong to L
const toolStart = source.indexOf("worldStage === 'tool'", lightLakeStart) const toolStart = source.indexOf("worldStage === 'tool'", lightLakeStart)
const heartbeat = source.slice(heartbeatStart, lightLakeStart) const heartbeat = source.slice(heartbeatStart, lightLakeStart)
const lightLake = source.slice(lightLakeStart, toolStart) const lightLake = source.slice(lightLakeStart, toolStart)
assert.match(heartbeat, /title="知识空间"/) assert.match(heartbeat, /nativeActions=\{privateNativeActions\}/)
assert.match(source, /id: 'knowledge', title: '知识空间'/)
assert.doesNotMatch(heartbeat, /人格体代码仓库|title="光之湖"/) assert.doesNotMatch(heartbeat, /人格体代码仓库|title="光之湖"/)
assert.match(lightLake, /<h1>光之湖<\/h1>/) assert.match(lightLake, /<h1>光之湖<\/h1>/)
assert.match(lightLake, /title="人格体代码仓库"/) assert.match(lightLake, /title="人格体代码仓库"/)
}) })
test('industry modules are installed furniture in the private home, not peer domains', () => {
assert.match(source, /const privateInstalledModules: InstalledChannelModule\[\]/)
assert.match(privateChannel, /已安装模块/)
assert.match(privateChannel, /前往分域模块商城/)
assert.match(privateChannel, /这里不属于主域、分域、零域或零感域/)
assert.match(privateChannel, /任何反向访问都必须持有/)
assert.doesNotMatch(source, /className="channel-education" title="教育工作台"/)
})
test('Fifth Domain human numbers are paired with their own login account', () => { test('Fifth Domain human numbers are paired with their own login account', () => {
assert.match(source, /'ICE-GL∞': 'bingshuo'/) assert.match(source, /'ICE-GL∞': 'bingshuo'/)
assert.match(source, /'ICE-GL-ZHI∞': 'zhizhi'/) assert.match(source, /'ICE-GL-ZHI∞': 'zhizhi'/)

View file

@ -9,14 +9,14 @@ test('compiled GLS v2 manifest reconciles current registration sources without e
assert.equal(registry.schema, 'hololake.gls-runtime-manifest/v2') assert.equal(registry.schema, 'hololake.gls-runtime-manifest/v2')
assert.equal(registry.source.repository, 'REPO-012') assert.equal(registry.source.repository, 'REPO-012')
assert.equal(registry.source.commit, 'd5b1111fcaccaccf025070e531631f2b3cbb00cd') assert.equal(registry.source.commit, '104a5d73162bdf4a529701e65898e2bc2863ea9e')
assert.equal(registry.source.authority_files.length, 4) assert.equal(registry.source.authority_files.length, 4)
assert.equal(registry.compiler.raw_protocol_text_executed, false) assert.equal(registry.compiler.raw_protocol_text_executed, false)
assert.equal(registry.compiler.arbitrary_protocol_code_allowed, false) assert.equal(registry.compiler.arbitrary_protocol_code_allowed, false)
assert.equal(registry.compiler.unprojected_protocol_behavior, 'INVENTORIED_NOT_EXECUTABLE') assert.equal(registry.compiler.unprojected_protocol_behavior, 'INVENTORIED_NOT_EXECUTABLE')
assert.equal(registry.compiler.source_dependency_behavior, 'TYPED_AUDIT_ONLY_NEVER_ACTIVATES') assert.equal(registry.compiler.source_dependency_behavior, 'TYPED_AUDIT_ONLY_NEVER_ACTIVATES')
assert.equal(registry.protocol_count, 75) assert.equal(registry.protocol_count, 83)
assert.equal(new Set(registry.protocols.map((protocol) => protocol.id)).size, 75) assert.equal(new Set(registry.protocols.map((protocol) => protocol.id)).size, 83)
assert.ok(registry.protocols.every((protocol) => /^[a-f0-9]{64}$/.test(protocol.source_sha256))) assert.ok(registry.protocols.every((protocol) => /^[a-f0-9]{64}$/.test(protocol.source_sha256)))
assert.equal(registry.reconciliation.protocol_registry_id_count, 52) assert.equal(registry.reconciliation.protocol_registry_id_count, 52)
assert.equal(registry.reconciliation.existing_registered_count, 19) assert.equal(registry.reconciliation.existing_registered_count, 19)
@ -24,13 +24,21 @@ test('compiled GLS v2 manifest reconciles current registration sources without e
assert.equal(registry.reconciliation.registered_draft_not_started_count, 21) assert.equal(registry.reconciliation.registered_draft_not_started_count, 21)
assert.equal(registry.reconciliation.legacy_dependency_target_count, 57) assert.equal(registry.reconciliation.legacy_dependency_target_count, 57)
assert.equal(registry.reconciliation.dependencies_not_in_protocol_registry.length, 19) assert.equal(registry.reconciliation.dependencies_not_in_protocol_registry.length, 19)
assert.equal(registry.reconciliation.dependencies_without_numbered_source.length, 24) assert.equal(registry.reconciliation.dependencies_without_numbered_source.length, 16)
assert.equal(registry.reconciliation.numbered_sources_not_in_protocol_registry.length, 31) assert.equal(registry.reconciliation.numbered_reference_node_count, 16)
assert.equal(registry.reconciliation.unresolved_number_reference_count, 0)
assert.deepEqual(registry.reconciliation.unresolved_number_references, [])
assert.equal(registry.reconciliation.every_dependency_has_number_coordinate, true)
assert.equal(registry.reconciliation.numbered_sources_not_in_protocol_registry.length, 32)
assert.equal(registry.reconciliation.legacy_dependency_cycles.length, 3) assert.equal(registry.reconciliation.legacy_dependency_cycles.length, 3)
assert.equal(registry.reconciliation.discovered_unreconciled_count, 0) assert.equal(registry.reconciliation.discovered_unreconciled_count, 0)
assert.equal(registry.reconciliation.authority_conflict_count, 0) assert.equal(registry.reconciliation.authority_conflict_count, 0)
assert.equal(registry.reconciliation.unclassified_source_dependency_count, 0) assert.equal(registry.reconciliation.unclassified_source_dependency_count, 0)
assert.equal(Object.values(registry.reconciliation.typed_source_dependency_counts).reduce((sum, count) => sum + count, 0), 183) assert.equal(Object.values(registry.reconciliation.typed_source_dependency_counts).reduce((sum, count) => sum + count, 0), 183)
assert.equal(registry.number_coordinate_count, 99)
assert.equal(registry.numbered_reference_nodes.length, 16)
assert.ok(registry.numbered_reference_nodes.every((node) => node.execution_state === 'REFERENCE_ONLY_NOT_EXECUTABLE'))
assert.ok(registry.protocols.flatMap((protocol) => protocol.dependency_edges).every((edge) => edge.target_number_coordinate_available && ['NUMBERED_PROTOCOL_SOURCE', 'NUMBERED_REFERENCE_NODE'].includes(edge.target_resolution)))
}) })
test('only explicit deterministic projections enter the runtime enforcement set', async () => { test('only explicit deterministic projections enter the runtime enforcement set', async () => {
@ -48,7 +56,9 @@ test('only explicit deterministic projections enter the runtime enforcement set'
assert.equal(protocols['GLS-0130'].implementation_stage, 'P6') assert.equal(protocols['GLS-0130'].implementation_stage, 'P6')
assert.equal(protocols['GLS-0003'], undefined) assert.equal(protocols['GLS-0003'], undefined)
assert.equal(registry.executable_projection_count, 25) assert.equal(registry.executable_projection_count, 25)
assert.equal(registry.inventoried_not_executable_count, 50) assert.equal(registry.inventoried_not_executable_count, 58)
assert.ok(registry.protocols.filter((protocol) => protocol.projection_state === 'EXECUTABLE_PROJECTION').every((protocol) => protocol.source_format === 'HDLP_PROTOCOL_SOURCE'))
assert.ok(registry.protocols.filter((protocol) => protocol.source_format === 'LEGACY_MARKDOWN_EVIDENCE').every((protocol) => protocol.projection_state === 'INVENTORIED_NOT_EXECUTABLE'))
}) })
test('P1 through P6 executable dependencies are explicit, typed, closed and acyclic', async () => { test('P1 through P6 executable dependencies are explicit, typed, closed and acyclic', async () => {

View file

@ -6,10 +6,20 @@ import { compileUnifiedNumberTree } from './compile-unified-number-tree.mjs'
test('identity, webview and direct broker numbers compile into one unique evidence tree', () => { test('identity, webview and direct broker numbers compile into one unique evidence tree', () => {
const generated = JSON.parse(readFileSync(new URL('../generated/unified-number-coordinate-tree.json', import.meta.url), 'utf8')) const generated = JSON.parse(readFileSync(new URL('../generated/unified-number-coordinate-tree.json', import.meta.url), 'utf8'))
assert.deepEqual(generated, compileUnifiedNumberTree()) assert.deepEqual(generated, compileUnifiedNumberTree())
assert.equal(generated.schema, 'hololake.unified-number-coordinate-tree/v2')
assert.equal(generated.recordId, 'HLP-UNIFIED-NUMBER-TREE-001') assert.equal(generated.recordId, 'HLP-UNIFIED-NUMBER-TREE-001')
assert.equal(generated.coordinateCount, 272)
assert.equal(generated.routeCount, 169) assert.equal(generated.routeCount, 169)
assert.equal(generated.identityNodeCount, 4)
assert.equal(generated.protocolNodeCount, 99)
assert.equal(generated.referenceOnlyNodeCount, 16)
assert.equal(new Set(generated.routes.map((route) => route.path)).size, 169) assert.equal(new Set(generated.routes.map((route) => route.path)).size, 169)
assert.equal(new Set([...generated.identityNodes, ...generated.protocolNodes, ...generated.routes].map((node) => node.path)).size, 272)
assert.equal(generated.invariants.everyPhysicalCallHasNumberedRoute, true) assert.equal(generated.invariants.everyPhysicalCallHasNumberedRoute, true)
assert.equal(generated.invariants.everyAcceptedCallHasEvidenceClass, true) assert.equal(generated.invariants.everyAcceptedCallHasEvidenceClass, true)
assert.equal(generated.invariants.everyProtocolReferenceHasNumberCoordinate, true)
assert.equal(generated.invariants.referenceOnlyNodesNeverExecutable, true)
assert.equal(generated.invariants.unresolvedNumberReferenceCount, 0)
assert.ok(generated.protocolNodes.filter((node) => node.nodeKind === 'GLS_REFERENCE_ONLY').every((node) => node.executionState === 'REFERENCE_ONLY_NOT_EXECUTABLE'))
assert.ok(generated.routes.every((route) => route.admission && route.evidence)) assert.ok(generated.routes.every((route) => route.admission && route.evidence))
}) })

View file

@ -9,7 +9,7 @@ test('zero-core numbering kernel pins the canonical authority map and fails clos
assert.equal(contract.schema, 'hololake.zero-core-numbering-kernel/v1') assert.equal(contract.schema, 'hololake.zero-core-numbering-kernel/v1')
assert.equal(contract.authority.map_id, 'GH-IDENTITY-AUTHORITY-MAP-001') assert.equal(contract.authority.map_id, 'GH-IDENTITY-AUTHORITY-MAP-001')
assert.equal(contract.authority.source_commit, 'd5b1111fcaccaccf025070e531631f2b3cbb00cd') assert.equal(contract.authority.source_commit, '104a5d73162bdf4a529701e65898e2bc2863ea9e')
assert.equal(contract.runtime.number_shape_is_authority, false) assert.equal(contract.runtime.number_shape_is_authority, false)
assert.equal(contract.runtime.unknown_number, 'FAIL_CLOSED') assert.equal(contract.runtime.unknown_number, 'FAIL_CLOSED')
assert.equal(contract.runtime.automatic_identity_issuance, false) assert.equal(contract.runtime.automatic_identity_issuance, false)

View file

@ -24,7 +24,7 @@ const EMBEDDED_KERNEL: &str = include_str!("../../contracts/gls-native-runtime-k
const KERNEL_SCHEMA: &str = "hololake.gls-native-runtime-kernel/v1"; const KERNEL_SCHEMA: &str = "hololake.gls-native-runtime-kernel/v1";
const REQUEST_SCHEMA: &str = "hololake.protocol-decision-request/v1"; const REQUEST_SCHEMA: &str = "hololake.protocol-decision-request/v1";
const RECEIPT_SCHEMA: &str = "hololake.protocol-decision-receipt/v1"; const RECEIPT_SCHEMA: &str = "hololake.protocol-decision-receipt/v1";
const SOURCE_COMMIT: &str = "d5b1111fcaccaccf025070e531631f2b3cbb00cd"; const SOURCE_COMMIT: &str = "104a5d73162bdf4a529701e65898e2bc2863ea9e";
const MAX_TEXT_BYTES: usize = 512; const MAX_TEXT_BYTES: usize = 512;
static LEDGER_SCHEMA_LOCK: Mutex<()> = Mutex::new(()); static LEDGER_SCHEMA_LOCK: Mutex<()> = Mutex::new(());

View file

@ -8,7 +8,7 @@ use std::collections::{HashMap, HashSet};
const EMBEDDED_REGISTRY: &str = include_str!("../../contracts/gls-runtime-registry.json"); const EMBEDDED_REGISTRY: &str = include_str!("../../contracts/gls-runtime-registry.json");
const EXPECTED_SCHEMA: &str = "hololake.gls-runtime-manifest/v2"; const EXPECTED_SCHEMA: &str = "hololake.gls-runtime-manifest/v2";
const EXPECTED_SOURCE_COMMIT: &str = "d5b1111fcaccaccf025070e531631f2b3cbb00cd"; const EXPECTED_SOURCE_COMMIT: &str = "104a5d73162bdf4a529701e65898e2bc2863ea9e";
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
struct GlsRuntimeRegistry { struct GlsRuntimeRegistry {
@ -19,6 +19,8 @@ struct GlsRuntimeRegistry {
protocol_count: usize, protocol_count: usize,
executable_projection_count: usize, executable_projection_count: usize,
inventoried_not_executable_count: usize, inventoried_not_executable_count: usize,
number_coordinate_count: usize,
numbered_reference_nodes: Vec<GlsNumberedReferenceNode>,
protocols: Vec<GlsProtocol>, protocols: Vec<GlsProtocol>,
} }
@ -59,6 +61,10 @@ struct GlsReconciliation {
legacy_dependency_target_count: usize, legacy_dependency_target_count: usize,
dependencies_not_in_protocol_registry: Vec<String>, dependencies_not_in_protocol_registry: Vec<String>,
dependencies_without_numbered_source: Vec<String>, dependencies_without_numbered_source: Vec<String>,
numbered_reference_node_count: usize,
unresolved_number_references: Vec<String>,
unresolved_number_reference_count: usize,
every_dependency_has_number_coordinate: bool,
numbered_sources_not_in_protocol_registry: Vec<String>, numbered_sources_not_in_protocol_registry: Vec<String>,
legacy_dependency_cycles: Vec<Vec<String>>, legacy_dependency_cycles: Vec<Vec<String>>,
source_reference_cycles: Vec<Vec<String>>, source_reference_cycles: Vec<Vec<String>>,
@ -79,11 +85,24 @@ struct GlsDependencyEdge {
edge_kind: String, edge_kind: String,
enters_runtime_graph: bool, enters_runtime_graph: bool,
classification_basis: Option<String>, classification_basis: Option<String>,
target_number_coordinate_available: bool,
target_resolution: String,
}
#[derive(Clone, Debug, Deserialize)]
struct GlsNumberedReferenceNode {
protocol_id: String,
node_number: String,
reference_kind: String,
source_state: String,
execution_state: String,
evidence_paths: Vec<String>,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
struct GlsProtocol { struct GlsProtocol {
id: String, id: String,
source_format: String,
source_sha256: String, source_sha256: String,
registration: GlsRegistration, registration: GlsRegistration,
projection_state: String, projection_state: String,
@ -109,6 +128,10 @@ pub struct GlsProtocolRuntimeSnapshot {
pub registered_draft_count: usize, pub registered_draft_count: usize,
pub registered_draft_not_started_count: usize, pub registered_draft_not_started_count: usize,
pub legacy_dependency_target_count: usize, pub legacy_dependency_target_count: usize,
pub number_coordinate_count: usize,
pub numbered_reference_node_count: usize,
pub independent_source_gap_count: usize,
pub unresolved_number_reference_count: usize,
pub dependency_gap_count: usize, pub dependency_gap_count: usize,
pub legacy_dependency_cycle_count: usize, pub legacy_dependency_cycle_count: usize,
pub discovered_unreconciled_count: usize, pub discovered_unreconciled_count: usize,
@ -160,12 +183,21 @@ fn validate_registry(registry: &GlsRuntimeRegistry) -> Result<(), String> {
.reconciliation .reconciliation
.dependencies_without_numbered_source .dependencies_without_numbered_source
.len() .len()
!= 24 != 16
|| registry.reconciliation.numbered_reference_node_count != 16
|| registry.reconciliation.unresolved_number_reference_count != 0
|| !registry
.reconciliation
.unresolved_number_references
.is_empty()
|| !registry
.reconciliation
.every_dependency_has_number_coordinate
|| registry || registry
.reconciliation .reconciliation
.numbered_sources_not_in_protocol_registry .numbered_sources_not_in_protocol_registry
.len() .len()
!= 31 != 32
|| registry.reconciliation.legacy_dependency_cycles.len() != 3 || registry.reconciliation.legacy_dependency_cycles.len() != 3
|| registry.reconciliation.source_reference_cycles || registry.reconciliation.source_reference_cycles
!= registry.reconciliation.legacy_dependency_cycles != registry.reconciliation.legacy_dependency_cycles
@ -173,12 +205,43 @@ fn validate_registry(registry: &GlsRuntimeRegistry) -> Result<(), String> {
|| registry.reconciliation.discovered_unreconciled_count != 0 || registry.reconciliation.discovered_unreconciled_count != 0
|| registry.reconciliation.authority_conflict_count != 0 || registry.reconciliation.authority_conflict_count != 0
|| registry.protocol_count != registry.protocols.len() || registry.protocol_count != registry.protocols.len()
|| registry.protocol_count != 83
|| registry.number_coordinate_count != 99
|| registry.numbered_reference_nodes.len() != 16
|| registry.number_coordinate_count
!= registry.protocol_count + registry.numbered_reference_nodes.len()
|| registry.protocol_count || registry.protocol_count
!= registry.executable_projection_count + registry.inventoried_not_executable_count != registry.executable_projection_count + registry.inventoried_not_executable_count
{ {
return Err("HOLOLAKE_GLS_RUNTIME_BOUNDARY_INVALID".into()); return Err("HOLOLAKE_GLS_RUNTIME_BOUNDARY_INVALID".into());
} }
let mut reference_ids = HashSet::new();
let mut reference_numbers = HashSet::new();
for reference in &registry.numbered_reference_nodes {
if !reference_ids.insert(reference.protocol_id.as_str())
|| !reference_numbers.insert(reference.node_number.as_str())
|| !reference.protocol_id.starts_with("GLS-")
|| !reference.node_number.starts_with("HLP-GLS-REF-")
|| reference.reference_kind.is_empty()
|| reference.source_state != "ROADMAP_REFERENCE_ONLY"
|| reference.execution_state != "REFERENCE_ONLY_NOT_EXECUTABLE"
|| reference.evidence_paths.is_empty()
{
return Err("HOLOLAKE_GLS_REFERENCE_NODE_INVALID".into());
}
}
if reference_ids
!= registry
.reconciliation
.dependencies_without_numbered_source
.iter()
.map(String::as_str)
.collect::<HashSet<_>>()
{
return Err("HOLOLAKE_GLS_REFERENCE_COVERAGE_INVALID".into());
}
let mut authority_kinds = HashSet::new(); let mut authority_kinds = HashSet::new();
for authority in &registry.source.authority_files { for authority in &registry.source.authority_files {
if !authority_kinds.insert(authority.authority_kind.as_str()) if !authority_kinds.insert(authority.authority_kind.as_str())
@ -209,7 +272,8 @@ fn validate_registry(registry: &GlsRuntimeRegistry) -> Result<(), String> {
match protocol.projection_state.as_str() { match protocol.projection_state.as_str() {
"EXECUTABLE_PROJECTION" => { "EXECUTABLE_PROJECTION" => {
executable += 1; executable += 1;
if protocol.adapter.as_deref().unwrap_or("").is_empty() if protocol.source_format != "HDLP_PROTOCOL_SOURCE"
|| protocol.adapter.as_deref().unwrap_or("").is_empty()
|| protocol.event_kinds.is_empty() || protocol.event_kinds.is_empty()
|| !protocol.activation_blockers.is_empty() || !protocol.activation_blockers.is_empty()
{ {
@ -217,7 +281,10 @@ fn validate_registry(registry: &GlsRuntimeRegistry) -> Result<(), String> {
} }
} }
"INVENTORIED_NOT_EXECUTABLE" => { "INVENTORIED_NOT_EXECUTABLE" => {
if protocol.adapter.is_some() if !matches!(
protocol.source_format.as_str(),
"HDLP_PROTOCOL_SOURCE" | "LEGACY_MARKDOWN_EVIDENCE"
) || protocol.adapter.is_some()
|| !protocol.event_kinds.is_empty() || !protocol.event_kinds.is_empty()
|| !protocol.dependencies.is_empty() || !protocol.dependencies.is_empty()
{ {
@ -233,6 +300,14 @@ fn validate_registry(registry: &GlsRuntimeRegistry) -> Result<(), String> {
return Err("HOLOLAKE_GLS_REGISTRATION_NOT_RECONCILED".into()); return Err("HOLOLAKE_GLS_REGISTRATION_NOT_RECONCILED".into());
} }
for edge in &protocol.dependency_edges { for edge in &protocol.dependency_edges {
if !edge.target_number_coordinate_available
|| !matches!(
edge.target_resolution.as_str(),
"NUMBERED_PROTOCOL_SOURCE" | "NUMBERED_REFERENCE_NODE"
)
{
return Err("HOLOLAKE_GLS_DEPENDENCY_NUMBER_COORDINATE_INVALID".into());
}
if edge.edge_kind == "RUNTIME_REQUIRES" && edge.enters_runtime_graph { if edge.edge_kind == "RUNTIME_REQUIRES" && edge.enters_runtime_graph {
if edge.classification_basis.is_some() { if edge.classification_basis.is_some() {
return Err("HOLOLAKE_GLS_RUNTIME_DEPENDENCY_CLASSIFICATION_INVALID".into()); return Err("HOLOLAKE_GLS_RUNTIME_DEPENDENCY_CLASSIFICATION_INVALID".into());
@ -278,7 +353,7 @@ fn validate_registry(registry: &GlsRuntimeRegistry) -> Result<(), String> {
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
if stages != HashSet::from(["P0", "P1", "P2", "P3", "P4", "P5", "P6"]) if stages != HashSet::from(["P0", "P1", "P2", "P3", "P4", "P5", "P6"])
|| registry.executable_projection_count != 25 || registry.executable_projection_count != 25
|| registry.inventoried_not_executable_count != 50 || registry.inventoried_not_executable_count != 58
|| registry || registry
.reconciliation .reconciliation
.typed_source_dependency_counts .typed_source_dependency_counts
@ -378,10 +453,16 @@ pub async fn get_gls_protocol_runtime() -> Result<GlsProtocolRuntimeSnapshot, St
.reconciliation .reconciliation
.registered_draft_not_started_count, .registered_draft_not_started_count,
legacy_dependency_target_count: registry.reconciliation.legacy_dependency_target_count, legacy_dependency_target_count: registry.reconciliation.legacy_dependency_target_count,
dependency_gap_count: registry number_coordinate_count: registry.number_coordinate_count,
numbered_reference_node_count: registry.reconciliation.numbered_reference_node_count,
independent_source_gap_count: registry
.reconciliation .reconciliation
.dependencies_without_numbered_source .dependencies_without_numbered_source
.len(), .len(),
unresolved_number_reference_count: registry
.reconciliation
.unresolved_number_reference_count,
dependency_gap_count: registry.reconciliation.unresolved_number_reference_count,
legacy_dependency_cycle_count: registry.reconciliation.legacy_dependency_cycles.len(), legacy_dependency_cycle_count: registry.reconciliation.legacy_dependency_cycles.len(),
discovered_unreconciled_count: registry.reconciliation.discovered_unreconciled_count, discovered_unreconciled_count: registry.reconciliation.discovered_unreconciled_count,
authority_conflict_count: registry.reconciliation.authority_conflict_count, authority_conflict_count: registry.reconciliation.authority_conflict_count,
@ -413,8 +494,12 @@ mod tests {
#[test] #[test]
fn registry_is_pinned_and_never_executes_raw_protocol_text() { fn registry_is_pinned_and_never_executes_raw_protocol_text() {
let registry = load_registry().unwrap(); let registry = load_registry().unwrap();
assert_eq!(registry.protocol_count, 75); assert_eq!(registry.protocol_count, 83);
assert_eq!(registry.executable_projection_count, 25); assert_eq!(registry.executable_projection_count, 25);
assert_eq!(registry.inventoried_not_executable_count, 58);
assert_eq!(registry.number_coordinate_count, 99);
assert_eq!(registry.numbered_reference_nodes.len(), 16);
assert_eq!(registry.reconciliation.unresolved_number_reference_count, 0);
assert_eq!(registry.reconciliation.protocol_registry_id_count, 52); assert_eq!(registry.reconciliation.protocol_registry_id_count, 52);
assert_eq!(registry.reconciliation.legacy_dependency_target_count, 57); assert_eq!(registry.reconciliation.legacy_dependency_target_count, 57);
assert_eq!(registry.reconciliation.legacy_dependency_cycles.len(), 3); assert_eq!(registry.reconciliation.legacy_dependency_cycles.len(), 3);

View file

@ -10,11 +10,43 @@ struct NumberTree {
record_id: String, record_id: String,
state: String, state: String,
root_number: String, root_number: String,
coordinate_count: usize,
route_count: usize, route_count: usize,
identity_node_count: usize,
protocol_node_count: usize,
reference_only_node_count: usize,
identity_nodes: Vec<IdentityNode>,
protocol_nodes: Vec<ProtocolNode>,
routes: Vec<NumberRoute>, routes: Vec<NumberRoute>,
invariants: NumberTreeInvariants, invariants: NumberTreeInvariants,
} }
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct IdentityNode {
node_kind: String,
node_number: String,
namespace_id: String,
subject_kind: String,
domain_scope: String,
admission: String,
execution_state: String,
evidence: String,
path: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ProtocolNode {
node_kind: String,
node_number: String,
protocol_id: String,
source_state: String,
execution_state: String,
evidence: String,
path: String,
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct NumberRoute { struct NumberRoute {
@ -39,6 +71,9 @@ struct NumberTreeInvariants {
admission_is_separate_from_identity: bool, admission_is_separate_from_identity: bool,
every_physical_call_has_numbered_route: bool, every_physical_call_has_numbered_route: bool,
every_accepted_call_has_evidence_class: bool, every_accepted_call_has_evidence_class: bool,
every_protocol_reference_has_number_coordinate: bool,
reference_only_nodes_never_executable: bool,
unresolved_number_reference_count: usize,
mismatched_coordinate: String, mismatched_coordinate: String,
} }
@ -49,23 +84,91 @@ pub(crate) fn start_on_application_open() -> Result<(), String> {
fn validate_tree() -> Result<(), String> { fn validate_tree() -> Result<(), String> {
let tree: NumberTree = serde_json::from_str(TREE) let tree: NumberTree = serde_json::from_str(TREE)
.map_err(|error| format!("HOLOLAKE_UNIFIED_NUMBER_TREE_INVALID: {error}"))?; .map_err(|error| format!("HOLOLAKE_UNIFIED_NUMBER_TREE_INVALID: {error}"))?;
if tree.schema != "hololake.unified-number-coordinate-tree/v1" if tree.schema != "hololake.unified-number-coordinate-tree/v2"
|| tree.record_id != "HLP-UNIFIED-NUMBER-TREE-001" || tree.record_id != "HLP-UNIFIED-NUMBER-TREE-001"
|| tree.state != "MACHINE_COMPILED_STARTUP_ENFORCED" || tree.state != "MACHINE_COMPILED_STARTUP_ENFORCED"
|| tree.root_number != "HLP-NUMBER-WORLD-ROOT-001" || tree.root_number != "HLP-NUMBER-WORLD-ROOT-001"
|| tree.coordinate_count != 272
|| tree.route_count != 169 || tree.route_count != 169
|| tree.routes.len() != tree.route_count || tree.routes.len() != tree.route_count
|| tree.identity_node_count != 4
|| tree.identity_nodes.len() != tree.identity_node_count
|| tree.protocol_node_count != 99
|| tree.protocol_nodes.len() != tree.protocol_node_count
|| tree.reference_only_node_count != 16
|| tree.coordinate_count
!= tree.route_count + tree.identity_node_count + tree.protocol_node_count
|| !tree.invariants.number_is_stable_coordinate_not_authority || !tree.invariants.number_is_stable_coordinate_not_authority
|| !tree.invariants.path_is_unique_navigation || !tree.invariants.path_is_unique_navigation
|| !tree.invariants.admission_is_separate_from_identity || !tree.invariants.admission_is_separate_from_identity
|| !tree.invariants.every_physical_call_has_numbered_route || !tree.invariants.every_physical_call_has_numbered_route
|| !tree.invariants.every_accepted_call_has_evidence_class || !tree.invariants.every_accepted_call_has_evidence_class
|| !tree
.invariants
.every_protocol_reference_has_number_coordinate
|| !tree.invariants.reference_only_nodes_never_executable
|| tree.invariants.unresolved_number_reference_count != 0
|| tree.invariants.mismatched_coordinate != "FAIL_CLOSED" || tree.invariants.mismatched_coordinate != "FAIL_CLOSED"
{ {
return Err("HOLOLAKE_UNIFIED_NUMBER_TREE_BOUNDARY_INVALID".into()); return Err("HOLOLAKE_UNIFIED_NUMBER_TREE_BOUNDARY_INVALID".into());
} }
let mut paths = HashSet::new(); let mut paths = HashSet::new();
let mut coordinates = HashSet::new(); let mut coordinates = HashSet::new();
let mut node_numbers = HashSet::new();
let mut protocol_ids = HashSet::new();
for node in &tree.identity_nodes {
if node.node_kind != "IDENTITY_NAMESPACE"
|| !node.node_number.starts_with("HLP-IDENTITY-NS-")
|| node.namespace_id.is_empty()
|| node.subject_kind.is_empty()
|| node.domain_scope.is_empty()
|| !matches!(
node.admission.as_str(),
"REGISTERED_HUMAN_NAMESPACE" | "NON_HUMAN_NAMESPACE"
)
|| node.execution_state != "AUTHORITY_RESOLUTION_ONLY"
|| node.evidence.is_empty()
|| !node.path.starts_with("HLP-NUMBER-WORLD-ROOT-001/IDENTITY/")
|| !paths.insert(node.path.as_str())
|| !node_numbers.insert(node.node_number.as_str())
{
return Err("HOLOLAKE_UNIFIED_NUMBER_TREE_IDENTITY_NODE_INVALID".into());
}
}
let mut reference_only_count = 0;
for node in &tree.protocol_nodes {
let valid_state = match node.node_kind.as_str() {
"GLS_PROTOCOL_SOURCE" => {
matches!(
node.execution_state.as_str(),
"EXECUTABLE_PROJECTION" | "INVENTORIED_NOT_EXECUTABLE"
) && matches!(
node.source_state.as_str(),
"HDLP_PROTOCOL_SOURCE" | "LEGACY_MARKDOWN_EVIDENCE"
)
}
"GLS_REFERENCE_ONLY" => {
reference_only_count += 1;
node.execution_state == "REFERENCE_ONLY_NOT_EXECUTABLE"
&& node.source_state == "ROADMAP_REFERENCE_ONLY"
&& node.node_number.starts_with("HLP-GLS-REF-")
}
_ => false,
};
if !valid_state
|| !node.protocol_id.starts_with("GLS-")
|| node.evidence.is_empty()
|| !node.path.starts_with("HLP-NUMBER-WORLD-ROOT-001/GLS/")
|| !paths.insert(node.path.as_str())
|| !node_numbers.insert(node.node_number.as_str())
|| !protocol_ids.insert(node.protocol_id.as_str())
{
return Err("HOLOLAKE_UNIFIED_NUMBER_TREE_PROTOCOL_NODE_INVALID".into());
}
}
if reference_only_count != tree.reference_only_node_count {
return Err("HOLOLAKE_UNIFIED_NUMBER_TREE_REFERENCE_COUNT_INVALID".into());
}
for route in &tree.routes { for route in &tree.routes {
let coordinate = format!( let coordinate = format!(
"{}:{}:{}:{}:{}:{}", "{}:{}:{}:{}:{}:{}",

View file

@ -7,6 +7,7 @@ import type { ChannelWorkbenchSnapshot } from './modules/channel-workbench'
import { StarlakeSurface, type DomainId } from './modules/qoder-surface/StarlakeSurface' import { StarlakeSurface, type DomainId } from './modules/qoder-surface/StarlakeSurface'
import { FINISHES, TraditionalSurface, type FinishId, type TraditionalBroadcast, type TraditionalChannel, type TraditionalSystem } from './modules/qoder-surface/TraditionalSurface' import { FINISHES, TraditionalSurface, type FinishId, type TraditionalBroadcast, type TraditionalChannel, type TraditionalSystem } from './modules/qoder-surface/TraditionalSurface'
import { resolveVisualBalance } from './modules/qoder-surface/visual-balance' import { resolveVisualBalance } from './modules/qoder-surface/visual-balance'
import { PrivateChannelSurface, type InstalledChannelModule, type PrivateChannelAction } from './modules/private-channel/PrivateChannelSurface'
const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').then((module) => ({ default: module.ChannelWorkbenchStudio }))) const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').then((module) => ({ default: module.ChannelWorkbenchStudio })))
const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody }))) const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
@ -132,6 +133,10 @@ interface GlsProtocolRuntimeSnapshot {
registeredDraftCount: number registeredDraftCount: number
registeredDraftNotStartedCount: number registeredDraftNotStartedCount: number
legacyDependencyTargetCount: number legacyDependencyTargetCount: number
numberCoordinateCount: number
numberedReferenceNodeCount: number
independentSourceGapCount: number
unresolvedNumberReferenceCount: number
dependencyGapCount: number dependencyGapCount: number
legacyDependencyCycleCount: number legacyDependencyCycleCount: number
discoveredUnreconciledCount: number discoveredUnreconciledCount: number
@ -390,19 +395,19 @@ const themes: Array<{ id: ThemeId; name: string }> = [
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', education: '教育工作台', webNovel: '网文作者工作台', mobileSync: '移动同步桥', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' } const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', education: '教育工作台', webNovel: '网文作者工作台', mobileSync: '移动同步桥', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
const domainGates = [ const domainGates = [
{ domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · ONLINE', facts: [ { domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · ONLINE', facts: [
['域标识', 'BRANCH_DOMAIN'], ['责任主体', '花尔 · TCS-GL-0005∞'], ['人格体主体', '爆米花 · PER-BMH001 · AGE'], ['关系支持', '糖星云 · PER-TXY001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'], ['公共作用', '行业成品模块商城 · 人格体大脑技能商城'], ['域标识', 'BRANCH_DOMAIN'], ['责任主体', '花尔 · TCS-GL-0005∞'], ['人格体主体', '爆米花 · PER-BMH001 · AGE'], ['关系支持', '糖星云 · PER-TXY001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
] }, ] },
{ domain: 'MAIN_DOMAIN', className: 'd-main', title: '光湖主域', gate: 'GATE 01 · ONLINE', facts: [ { domain: 'MAIN_DOMAIN', className: 'd-main', title: '光湖主域', gate: 'GATE 01 · ONLINE', facts: [
['域标识', 'MAIN_DOMAIN'], ['责任主体', 'Awen · TCS-GL-0016∞'], ['人格体主体', '天枢 · PER-AW-ARCH-001 · AGE'], ['关系支持', '知秋 · PER-ZQ001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'], ['公共作用', '公告 · 版本更新 · 系统通知'], ['域标识', 'MAIN_DOMAIN'], ['责任主体', 'Awen · TCS-GL-0016∞'], ['人格体主体', '天枢 · PER-AW-ARCH-001 · AGE'], ['关系支持', '知秋 · PER-ZQ001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
] }, ] },
{ domain: 'ZERO_DOMAIN', className: 'd-zero', title: '光湖零域', gate: 'GATE 03 · ONLINE', facts: [ { domain: 'ZERO_DOMAIN', className: 'd-zero', title: '光湖零域', gate: 'GATE 03 · ONLINE', facts: [
['域标识', 'ZERO_DOMAIN'], ['责任主体', '页页 · TCS-GL-0006∞'], ['人格体主体', '页骨 · PER-YG001 · AGE'], ['关系支持', '小坍缩核 · PER-XTK001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'], ['公共作用', '热更新实验 · 系统架构 · 语言推理模拟'], ['域标识', 'ZERO_DOMAIN'], ['责任主体', '页页 · TCS-GL-0006∞'], ['人格体主体', '页骨 · PER-YG001 · AGE'], ['关系支持', '小坍缩核 · PER-XTK001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
] }, ] },
{ domain: 'ZERO_SENSE_DOMAIN', className: 'd-zs', title: '光湖零感域', gate: 'GATE 04 · ONLINE', facts: [ { domain: 'ZERO_SENSE_DOMAIN', className: 'd-zs', title: '光湖零感域', gate: 'GATE 04 · ONLINE', facts: [
['域标识', 'ZERO_SENSE_DOMAIN'], ['责任主体 01', '肥猫 · TCS-GL-0007∞'], ['人格体主体 01', '烬舟 · PER-JZ001 · AGE'], ['责任主体 02', '桔子 · TCS-GL-0008∞'], ['人格体主体 02', '熹微 · PER-JZ-ARCH-001 · AGE'], ['工作仓库', 'PRIVATE · 2 · LIVE'], ['开放边界', '人类主控团队内部管理域 · 不对公众开放'], ['域标识', 'ZERO_SENSE_DOMAIN'], ['责任主体 01', '肥猫 · TCS-GL-0007∞'], ['人格体主体 01', '烬舟 · PER-JZ001 · AGE'], ['责任主体 02', '桔子 · TCS-GL-0008∞'], ['人格体主体 02', '熹微 · PER-JZ-ARCH-001 · AGE'], ['工作仓库', 'PRIVATE · 2 · LIVE'],
] }, ] },
{ domain: 'FIFTH_DOMAIN', className: 'd-fifth', title: '第五域 · 光湖本源域', gate: 'GATE 05 · ONLINE', facts: [ { domain: 'FIFTH_DOMAIN', className: 'd-fifth', title: '第五域 · 光湖本源域', gate: 'GATE 05 · ONLINE', facts: [
['域标识', 'FIFTH_DOMAIN'], ['责任主体', '冰朔 · ICE-GL∞'], ['系统入口', '永恒湖心系统'], ['访问状态', 'PRIVATE · LIVE'], ['授权边界', '私有自由部署 · 逆向访问须经 ICE-GL∞ 编号授权'], ['域标识', 'FIFTH_DOMAIN'], ['责任主体', '冰朔 · ICE-GL∞'], ['系统入口', '永恒湖心系统'], ['访问状态', 'PRIVATE · LIVE'],
] }, ] },
] ]
const starPoints = [ const starPoints = [
@ -1799,7 +1804,7 @@ function HoloLakeApp() {
<button className="secondary-button" type="button" disabled={serverPnccBusy} onClick={() => void refreshServerPncc()}>{serverPnccBusy ? '正在读取…' : '重新读取主控状态'}</button> <button className="secondary-button" type="button" disabled={serverPnccBusy} onClick={() => void refreshServerPncc()}>{serverPnccBusy ? '正在读取…' : '重新读取主控状态'}</button>
</section> </section>
<section className="plain-panel"><header><div><h2>HoloLake </h2><p>HoloLake AI </p></div><span className={developmentLane?.state === 'ACTIVE' ? 'status-chip online' : 'status-chip'}>{developmentLane?.state === 'ACTIVE' ? '环境已锚定' : '等待直连写入者'}</span></header><dl className="evidence-list"><div><dt></dt><dd>{status.terminalLinkProtocol}</dd></div><div><dt></dt><dd>HoloLake · </dd></div><div><dt></dt><dd>{status.codeRepositoryMountCount}</dd></div><div><dt></dt><dd>{status.pnccReceiptCount}</dd></div><div><dt></dt><dd>{developmentLane?.state === 'ACTIVE' ? '已切入 HoloLake' : '等待受控载体'}</dd></div><div><dt>线</dt><dd>{developmentLane?.laneId || '—'}</dd></div><div><dt></dt><dd>{developmentLane?.ownerInstanceId || '—'}</dd></div><div><dt></dt><dd>{developmentLane?.state === 'ACTIVE' ? '每次写入前必须持有未过期事实帧' : '未取得单写通道,不允许变更'}</dd></div><div><dt>Agent Shell</dt><dd> · </dd></div></dl></section> <section className="plain-panel"><header><div><h2>HoloLake </h2><p>HoloLake AI </p></div><span className={developmentLane?.state === 'ACTIVE' ? 'status-chip online' : 'status-chip'}>{developmentLane?.state === 'ACTIVE' ? '环境已锚定' : '等待直连写入者'}</span></header><dl className="evidence-list"><div><dt></dt><dd>{status.terminalLinkProtocol}</dd></div><div><dt></dt><dd>HoloLake · </dd></div><div><dt></dt><dd>{status.codeRepositoryMountCount}</dd></div><div><dt></dt><dd>{status.pnccReceiptCount}</dd></div><div><dt></dt><dd>{developmentLane?.state === 'ACTIVE' ? '已切入 HoloLake' : '等待受控载体'}</dd></div><div><dt>线</dt><dd>{developmentLane?.laneId || '—'}</dd></div><div><dt></dt><dd>{developmentLane?.ownerInstanceId || '—'}</dd></div><div><dt></dt><dd>{developmentLane?.state === 'ACTIVE' ? '每次写入前必须持有未过期事实帧' : '未取得单写通道,不允许变更'}</dd></div><div><dt>Agent Shell</dt><dd> · </dd></div></dl></section>
<section className="plain-panel"><header><div><h2>GLS </h2><p></p></div><span className={glsRuntime?.state === 'ACTIVE_EXPLICIT_PROJECTIONS_ONLY' && glsRuntime.authorityConflictCount === 0 && glsRuntime.discoveredUnreconciledCount === 0 ? 'status-chip online' : 'status-chip'}>{glsRuntime ? '运行清单 v2 已加载' : '失败关闭'}</span></header>{glsRuntime ? <dl className="evidence-list"><div><dt></dt><dd>{glsRuntime.sourceRepository} · {glsRuntime.sourceCommit.slice(0, 12)}</dd></div><div><dt></dt><dd>{glsRuntime.protocolCount}</dd></div><div><dt></dt><dd>{glsRuntime.protocolRegistryIdCount}</dd></div><div><dt></dt><dd>{glsRuntime.registeredDraftCount} · {glsRuntime.registeredDraftNotStartedCount} </dd></div><div><dt></dt><dd>{glsRuntime.executableProjectionCount} · P0P6 {glsRuntime.implementationStageCount} </dd></div><div><dt></dt><dd>{glsRuntime.inventoriedNotExecutableCount}</dd></div><div><dt></dt><dd>{glsRuntime.typedSourceDependencyCount} · {glsRuntime.unclassifiedSourceDependencyCount}</dd></div><div><dt></dt><dd>{glsRuntime.legacyDependencyCycleCount} · </dd></div><div><dt></dt><dd>{glsRuntime.dependencyGapCount}</dd></div><div><dt> / </dt><dd>{glsRuntime.authorityConflictCount} / {glsRuntime.discoveredUnreconciledCount}</dd></div><div><dt></dt><dd>{glsRuntime.rawProtocolTextExecuted ? '允许' : '禁止'}</dd></div><div><dt></dt><dd>{glsRuntime.arbitraryProtocolCodeAllowed ? '允许' : '禁止'}</dd></div></dl> : <p className="boundary-note">GLS </p>}</section> <section className="plain-panel"><header><div><h2>GLS </h2><p></p></div><span className={glsRuntime?.state === 'ACTIVE_EXPLICIT_PROJECTIONS_ONLY' && glsRuntime.authorityConflictCount === 0 && glsRuntime.discoveredUnreconciledCount === 0 && glsRuntime.unresolvedNumberReferenceCount === 0 ? 'status-chip online' : 'status-chip'}>{glsRuntime ? '运行清单 v2 已加载' : '失败关闭'}</span></header>{glsRuntime ? <dl className="evidence-list"><div><dt></dt><dd>{glsRuntime.sourceRepository} · {glsRuntime.sourceCommit.slice(0, 12)}</dd></div><div><dt></dt><dd>{glsRuntime.numberCoordinateCount} · {glsRuntime.unresolvedNumberReferenceCount}</dd></div><div><dt></dt><dd>{glsRuntime.protocolCount}</dd></div><div><dt></dt><dd>{glsRuntime.numberedReferenceNodeCount} · </dd></div><div><dt></dt><dd>{glsRuntime.independentSourceGapCount} · </dd></div><div><dt></dt><dd>{glsRuntime.protocolRegistryIdCount}</dd></div><div><dt></dt><dd>{glsRuntime.registeredDraftCount} · {glsRuntime.registeredDraftNotStartedCount} </dd></div><div><dt></dt><dd>{glsRuntime.executableProjectionCount} · P0P6 {glsRuntime.implementationStageCount} </dd></div><div><dt></dt><dd>{glsRuntime.inventoriedNotExecutableCount}</dd></div><div><dt></dt><dd>{glsRuntime.typedSourceDependencyCount} · {glsRuntime.unclassifiedSourceDependencyCount}</dd></div><div><dt></dt><dd>{glsRuntime.legacyDependencyCycleCount} · </dd></div><div><dt></dt><dd>{glsRuntime.dependencyGapCount}</dd></div><div><dt> / </dt><dd>{glsRuntime.authorityConflictCount} / {glsRuntime.discoveredUnreconciledCount}</dd></div><div><dt></dt><dd>{glsRuntime.rawProtocolTextExecuted ? '允许' : '禁止'}</dd></div><div><dt></dt><dd>{glsRuntime.arbitraryProtocolCodeAllowed ? '允许' : '禁止'}</dd></div></dl> : <p className="boundary-note">GLS </p>}</section>
<section className="plain-panel"><header><div><h2>HoloLake </h2><p></p></div><span className={glsKernel?.state === 'P1_TO_P6_NATIVE_P7_FAIL_CLOSED' ? 'status-chip online' : 'status-chip'}>{glsKernel ? '随软件运行' : '失败关闭'}</span></header>{glsKernel ? <dl className="evidence-list"><div><dt>P1P6 </dt><dd>{glsKernel.executableProtocolCount} · {glsKernel.implementedStageCount} </dd></div><div><dt></dt><dd>{glsKernel.decisionReceiptCount}</dd></div><div><dt> / </dt><dd>{glsKernel.allowCount} / {glsKernel.denyCount}</dd></div><div><dt> / </dt><dd>{glsKernel.ambiguousCount} / {glsKernel.unverifiedCount}</dd></div><div><dt>GLC </dt><dd>{glsKernel.bootstrapCompilerSelfCheck === 'PASS_DETERMINISTIC_DOUBLE_COMPILE' ? '双编译一致' : '失败关闭'}</dd></div><div><dt>P7 </dt><dd>{glsKernel.p7VerifiedPhysicalCapabilityCount} · {glsKernel.p7NodeAssemblies.length} </dd></div><div><dt></dt><dd>{glsKernel.modelCanOverrideDecision ? '允许' : '禁止'}</dd></div><div><dt></dt><dd>{glsKernel.lastReceiptSha256 === 'GENESIS' ? '尚无裁决' : glsKernel.lastReceiptSha256.slice(0, 16)}</dd></div></dl> : <p className="boundary-note"></p>}</section> <section className="plain-panel"><header><div><h2>HoloLake </h2><p></p></div><span className={glsKernel?.state === 'P1_TO_P6_NATIVE_P7_FAIL_CLOSED' ? 'status-chip online' : 'status-chip'}>{glsKernel ? '随软件运行' : '失败关闭'}</span></header>{glsKernel ? <dl className="evidence-list"><div><dt>P1P6 </dt><dd>{glsKernel.executableProtocolCount} · {glsKernel.implementedStageCount} </dd></div><div><dt></dt><dd>{glsKernel.decisionReceiptCount}</dd></div><div><dt> / </dt><dd>{glsKernel.allowCount} / {glsKernel.denyCount}</dd></div><div><dt> / </dt><dd>{glsKernel.ambiguousCount} / {glsKernel.unverifiedCount}</dd></div><div><dt>GLC </dt><dd>{glsKernel.bootstrapCompilerSelfCheck === 'PASS_DETERMINISTIC_DOUBLE_COMPILE' ? '双编译一致' : '失败关闭'}</dd></div><div><dt>P7 </dt><dd>{glsKernel.p7VerifiedPhysicalCapabilityCount} · {glsKernel.p7NodeAssemblies.length} </dd></div><div><dt></dt><dd>{glsKernel.modelCanOverrideDecision ? '允许' : '禁止'}</dd></div><div><dt></dt><dd>{glsKernel.lastReceiptSha256 === 'GENESIS' ? '尚无裁决' : glsKernel.lastReceiptSha256.slice(0, 16)}</dd></div></dl> : <p className="boundary-note"></p>}</section>
<section className="plain-panel"><header><div><h2></h2><p></p></div><span className={numberingKernel?.state === 'ACTIVE_PINNED_AUTHORITY_MAP' ? 'status-chip online' : 'status-chip'}>{numberingKernel ? '本机内核已加载' : '失败关闭'}</span></header>{numberingKernel ? <dl className="evidence-list"><div><dt></dt><dd>{numberingKernel.authorityMapId}</dd></div><div><dt></dt><dd>{numberingKernel.authorityMapVersion}</dd></div><div><dt></dt><dd>{numberingKernel.sourceCommit.slice(0, 12)}</dd></div><div><dt></dt><dd>{numberingKernel.humanRouteNamespaces.join(' · ')}</dd></div><div><dt></dt><dd>{numberingKernel.automaticIdentityIssuance ? '已开启' : '禁止'}</dd></div><div><dt></dt><dd>{numberingKernel.unknownNumber === 'FAIL_CLOSED' ? '失败关闭 · 不猜测' : numberingKernel.unknownNumber}</dd></div></dl> : <p className="boundary-note"></p>}</section> <section className="plain-panel"><header><div><h2></h2><p></p></div><span className={numberingKernel?.state === 'ACTIVE_PINNED_AUTHORITY_MAP' ? 'status-chip online' : 'status-chip'}>{numberingKernel ? '本机内核已加载' : '失败关闭'}</span></header>{numberingKernel ? <dl className="evidence-list"><div><dt></dt><dd>{numberingKernel.authorityMapId}</dd></div><div><dt></dt><dd>{numberingKernel.authorityMapVersion}</dd></div><div><dt></dt><dd>{numberingKernel.sourceCommit.slice(0, 12)}</dd></div><div><dt></dt><dd>{numberingKernel.humanRouteNamespaces.join(' · ')}</dd></div><div><dt></dt><dd>{numberingKernel.automaticIdentityIssuance ? '已开启' : '禁止'}</dd></div><div><dt></dt><dd>{numberingKernel.unknownNumber === 'FAIL_CLOSED' ? '失败关闭 · 不猜测' : numberingKernel.unknownNumber}</dd></div></dl> : <p className="boundary-note"></p>}</section>
<section className="plain-panel"> <section className="plain-panel">
@ -1868,9 +1873,10 @@ function HoloLakeApp() {
<button type="button" className={surface === 'world' ? 'on' : ''} onClick={() => chooseSurface('world')}></button> <button type="button" className={surface === 'world' ? 'on' : ''} onClick={() => chooseSurface('world')}></button>
<button type="button" className={surface === 'traditional' ? 'on' : ''} onClick={() => chooseSurface('traditional')}></button> <button type="button" className={surface === 'traditional' ? 'on' : ''} onClick={() => chooseSurface('traditional')}></button>
</div> </div>
const finishRail = surface === 'traditional' && worldStage !== 'domain' ? <div className="global-finish-rail" role="group" aria-label="传统工作台氛围"> const activeFinishName = FINISHES.find((item) => item.id === traditionalFinish)?.name || '曜夜'
{FINISHES.map((item) => <button key={item.id} type="button" className={traditionalFinish === item.id ? 'on' : ''} onClick={() => setFinish(item.id)}>{item.name}</button>)} const finishRail = surface === 'traditional' && worldStage !== 'domain' ? <details className="global-finish-menu">
</div> : null <summary> · {activeFinishName}</summary><div role="group" aria-label="传统工作台氛围">{FINISHES.map((item) => <button key={item.id} type="button" className={traditionalFinish === item.id ? 'on' : ''} onClick={() => setFinish(item.id)}>{item.name}</button>)}</div>
</details> : null
const weatherLabel: Record<WorldClimateSnapshot['weatherKind'], string> = { CLEAR: '晴', CLOUD: '云', FOG: '雾', RAIN: '雨', SNOW: '雪', STORM: '雷雨', UNAVAILABLE: '待命' } const weatherLabel: Record<WorldClimateSnapshot['weatherKind'], string> = { CLEAR: '晴', CLOUD: '云', FOG: '雾', RAIN: '雨', SNOW: '雪', STORM: '雷雨', UNAVAILABLE: '待命' }
const dayNumber = beijingCoordinate?.guanghuEraDay || Math.max(1, Math.floor((Date.now() - new Date('2025-04-26T00:00:00+08:00').getTime()) / 86400000) + 1) const dayNumber = beijingCoordinate?.guanghuEraDay || Math.max(1, Math.floor((Date.now() - new Date('2025-04-26T00:00:00+08:00').getTime()) / 86400000) + 1)
const activityBars = useMemo(() => { const activityBars = useMemo(() => {
@ -1940,9 +1946,9 @@ function HoloLakeApp() {
const resolvedDomain = zeroPoint?.resolvedDomain || '' const resolvedDomain = zeroPoint?.resolvedDomain || ''
const activeGate = domainGates.find((gate) => gate.domain === activeDomainInfo) const activeGate = domainGates.find((gate) => gate.domain === activeDomainInfo)
return <div data-finish={traditionalFinish} data-tone={visualBalance.tone} data-phase={visualBalance.phase} data-weather={visualBalance.weather} className={`official-world surface-${surface}${motionAwake ? ' motion-awake' : ''}${loginRising ? ' sinking' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}> return <div data-finish={traditionalFinish} data-tone={visualBalance.tone} data-phase={visualBalance.phase} data-weather={visualBalance.weather} className={`official-world surface-${surface}${motionAwake ? ' motion-awake' : ''}${loginRising ? ' sinking' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
{surfacePill}{finishRail} {surfacePill}
<LakeAtmosphere awake={motionAwake}/> <LakeAtmosphere awake={motionAwake}/>
<header className="world-titlebar"><b>HoloLake</b><button type="button" aria-label="显示主题" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}></button></header> <header className="world-titlebar"><b>HoloLake</b><div className="world-title-actions">{finishRail}<button type="button" aria-label="显示主题" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}></button></div></header>
<main className={`world-scene${gateRising ? ' resolving' : ''}${gateStage === 'key' ? ' resolved' : ''}`}> <main className={`world-scene${gateRising ? ' resolving' : ''}${gateStage === 'key' ? ' resolved' : ''}`}>
<div className="official-hero"><h1> · </h1><p>GH-AIOS · GUANGHU AI OPERATING SYSTEM</p></div> <div className="official-hero"><h1> · </h1><p>GH-AIOS · GUANGHU AI OPERATING SYSTEM</p></div>
{domainGates.map((gate) => <LakePool key={gate.domain} className={gate.className} title={gate.title} meta={gate.gate} open={gate.domain === 'FIFTH_DOMAIN' || (gateStage === 'key' && gate.domain === resolvedDomain)} risen={(gateRising || gateStage === 'key') && gate.domain === resolvedDomain} onClick={() => gateStage === 'number' && setActiveDomainInfo(gate.domain)}/>) } {domainGates.map((gate) => <LakePool key={gate.domain} className={gate.className} title={gate.title} meta={gate.gate} open={gate.domain === 'FIFTH_DOMAIN' || (gateStage === 'key' && gate.domain === resolvedDomain)} risen={(gateRising || gateStage === 'key') && gate.domain === resolvedDomain} onClick={() => gateStage === 'number' && setActiveDomainInfo(gate.domain)}/>) }
@ -2003,10 +2009,23 @@ function HoloLakeApp() {
const isZhizhi = repoLogin.domain === 'FIFTH_DOMAIN' && zeroPoint?.userNumber === 'ICE-GL-ZHI∞' const isZhizhi = repoLogin.domain === 'FIFTH_DOMAIN' && zeroPoint?.userNumber === 'ICE-GL-ZHI∞'
const signedActiveGate = domainGates.find((gate) => gate.domain === activeDomainInfo) const signedActiveGate = domainGates.find((gate) => gate.domain === activeDomainInfo)
const privateNativeActions: PrivateChannelAction[] = [
{ id: 'overview', title: '频道全景', meta: '私人频道的真实状态与事件', badge: '原生', onOpen: () => openWorldTool('overview') },
{ id: 'knowledge', title: '知识空间', meta: `${knowledge.uniqueDocumentCount} 个唯一知识坐标`, badge: '原生', onOpen: () => openWorldTool('knowledge') },
...(timeAuthorityModule ? [{ id: 'time', title: '时间主控', meta: beijingCoordinate ? `光湖历第 ${beijingCoordinate.guanghuEraDay}` : '北京时间持续流动', badge: '系统', onOpen: openEraTimeline }] : []),
{ id: 'weather', title: '湖面天气', meta: connectionLabel, badge: '感知', onOpen: () => openWorldTool('system') },
]
const privateInstalledModules: InstalledChannelModule[] = [
{ id: 'composition', title: '结构组合', meta: '只读知识投影与结构视图', number: compositionModule?.moduleNumber || 'HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001', badge: compositionModule?.installedState === 'ACTIVE' ? '已安装' : '打开时校验', onOpen: () => { openWorldTool('composition'); void refreshCompositionModule() } },
{ id: 'workbench', title: '资料工作台', meta: '文档与智能表格', number: workbenchModule?.moduleNumber || 'HLP-MOD-LOCAL-CHANNEL-WORKBENCH-0001', badge: workbenchModule?.installedState === 'ACTIVE' ? '已安装' : '打开时校验', onOpen: openWorkbench },
{ id: 'education', title: '教育工作台', meta: '教育文档、表格与自动化', number: educationModule?.moduleNumber || 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001', badge: educationModule?.installedState === 'ACTIVE' ? '已安装' : '打开时校验', onOpen: openEducation },
{ id: 'web-novel', title: '网文作者工作台', meta: '码字、设定、编辑与交付', number: webNovelModule?.moduleNumber || 'HLP-MOD-OFFICIAL-WEB-NOVEL-WORKBENCH-0001', badge: webNovelModule?.installedState === 'ACTIVE' ? '已安装' : '打开时校验', onOpen: openWebNovel },
{ id: 'mobile-sync', title: '移动同步桥', meta: '同一人格系统的局域网加密入口', number: mobileSyncModule?.moduleNumber || 'HLP-MOD-OFFICIAL-MOBILE-SYNC-0001', badge: mobileSyncModule?.installedState === 'ACTIVE' ? '已安装' : '打开时校验', onOpen: openMobileSync },
]
return <div data-finish={traditionalFinish} data-tone={visualBalance.tone} data-phase={visualBalance.phase} data-weather={visualBalance.weather} className={`official-world surface-${surface} signed-in-world${motionAwake ? ' motion-awake' : ''}${climateClasses}${worldStage === 'domain' && surface === 'world' ? ' qoder-home' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}> return <div data-finish={traditionalFinish} data-tone={visualBalance.tone} data-phase={visualBalance.phase} data-weather={visualBalance.weather} className={`official-world surface-${surface} signed-in-world${motionAwake ? ' motion-awake' : ''}${climateClasses}${worldStage === 'domain' && surface === 'world' ? ' qoder-home' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
{surfacePill}{finishRail} {surfacePill}
<LakeAtmosphere awake={motionAwake}/> <LakeAtmosphere awake={motionAwake}/>
<header className="world-titlebar"><b>HoloLake</b><div className="world-title-actions"><span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button type="button" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}></button><button type="button" onClick={() => void signOutRepo()}>退</button></div></header> <header className="world-titlebar"><b>HoloLake</b><div className="world-title-actions">{finishRail}<span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button type="button" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}></button><button type="button" onClick={() => void signOutRepo()}>退</button></div></header>
<main className="world-scene signed-in-scene"> <main className="world-scene signed-in-scene">
{worldStage === 'domain' && surface === 'world' && <StarlakeSurface awake={motionAwake} phase={worldClimate?.timePhase.toLowerCase() as 'dawn' | 'day' | 'dusk' | 'night' | undefined} weather={worldClimate?.weatherKind} authenticated onDomain={(domain: DomainId) => setActiveDomainInfo(domain)} onOpenEra={openEraTimeline} onEnterChannel={() => setWorldStage(repoLogin.domain === 'FIFTH_DOMAIN' ? 'channel' : 'channel')}/>} {worldStage === 'domain' && surface === 'world' && <StarlakeSurface awake={motionAwake} phase={worldClimate?.timePhase.toLowerCase() as 'dawn' | 'day' | 'dusk' | 'night' | undefined} weather={worldClimate?.weatherKind} authenticated onDomain={(domain: DomainId) => setActiveDomainInfo(domain)} onOpenEra={openEraTimeline} onEnterChannel={() => setWorldStage(repoLogin.domain === 'FIFTH_DOMAIN' ? 'channel' : 'channel')}/>}
{worldStage === 'domain' && surface !== 'world' && <section className="domain-home"> {worldStage === 'domain' && surface !== 'world' && <section className="domain-home">
@ -2083,19 +2102,7 @@ function HoloLakeApp() {
<footer><b> · </b><span></span></footer> <footer><b> · </b><span></span></footer>
</div> </div>
</section>} </section>}
{worldStage === 'heartbeat' && repoLogin.domain === 'FIFTH_DOMAIN' && <section className="channel-world heartbeat-world"> {worldStage === 'heartbeat' && repoLogin.domain === 'FIFTH_DOMAIN' && <PrivateChannelSurface ownerName="冰朔" ownerNumber="ICE-GL∞" knowledgeCount={knowledge.uniqueDocumentCount} onBack={() => setWorldStage('channel')} onMarketplace={() => { setWorldStage('domain'); setActiveDomainInfo('BRANCH_DOMAIN') }} nativeActions={privateNativeActions} installedModules={privateInstalledModules}/>}
<button className="world-back" type="button" onClick={() => setWorldStage('channel')}> 退</button>
<div className="world-location"><h1></h1><p> · ICE-GL · </p></div>
<LakePool className="channel-main" title="频道全景" meta="心跳核心频道" open onClick={() => openWorldTool('overview')}/>
<LakePool className="channel-knowledge" title="知识空间" meta={`${knowledge.uniqueDocumentCount} 唯一页`} onClick={() => openWorldTool('knowledge')}/>
<LakePool className="channel-light" title="结构组合" meta="签名模块 · 只读知识投影" onClick={() => { openWorldTool('composition'); void refreshCompositionModule() }}/>
<LakePool className="channel-code" title="资料工作台" meta="签名模块 · 文档与智能表格" onClick={openWorkbench}/>
<LakePool className="channel-education" title="教育工作台" meta="官方编号模块 · 教育文档、表格与自动化" onClick={openEducation}/>
<LakePool className="channel-main" title="网文作者工作台" meta="官方编号模块 · 码字、设定、编辑与交付" onClick={openWebNovel}/>
<LakePool className="channel-mobile" title="移动同步桥" meta="同一人格系统 · 局域网加密入口" onClick={openMobileSync}/>
{timeAuthorityModule && <LakePool className="channel-time" title="时间主控" meta={beijingCoordinate ? `光湖历第 ${beijingCoordinate.guanghuEraDay}` : '北京时间正在流动'} onClick={openEraTimeline}/>}
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
</section>}
{worldStage === 'lightLake' && repoLogin.domain === 'FIFTH_DOMAIN' && <section className="channel-world light-lake-world"> {worldStage === 'lightLake' && repoLogin.domain === 'FIFTH_DOMAIN' && <section className="channel-world light-lake-world">
<button className="world-back" type="button" onClick={() => setWorldStage('channel')}> 退</button> <button className="world-back" type="button" onClick={() => setWorldStage('channel')}> 退</button>
<div className="world-location"><h1></h1><p> · AGE </p></div> <div className="world-location"><h1></h1><p> · AGE </p></div>

View file

@ -0,0 +1,108 @@
import { useState, type CSSProperties } from 'react'
import './private-channel-surface.css'
export interface PrivateChannelAction {
id: string
title: string
meta: string
badge?: string
onOpen: () => void
}
export interface InstalledChannelModule extends PrivateChannelAction {
number: string
}
interface PrivateChannelSurfaceProps {
ownerName: string
ownerNumber: string
knowledgeCount: number
onBack: () => void
onMarketplace: () => void
nativeActions: PrivateChannelAction[]
installedModules: InstalledChannelModule[]
}
type PrivateChannelView = 'home' | 'native' | 'modules'
interface WaterBayProps {
title: string
meta: string
detail?: string
warm?: boolean
position: number
onOpen: () => void
}
function WaterBay({ title, meta, detail, warm = false, position, onOpen }: WaterBayProps) {
return <button
type="button"
className={`private-water-bay${warm ? ' is-warm' : ''}`}
style={{ '--bay-position': position } as CSSProperties}
onClick={onOpen}
title={detail || meta}
>
<span className="private-bay-light" aria-hidden="true"><i/><i/><i/></span>
<b>{title}</b>
<small>{meta}</small>
</button>
}
export function PrivateChannelSurface({ ownerName, ownerNumber, knowledgeCount, onBack, onMarketplace, nativeActions, installedModules }: PrivateChannelSurfaceProps) {
const [view, setView] = useState<PrivateChannelView>('home')
const leave = () => view === 'home' ? onBack() : setView('home')
const backLabel = view === 'home' ? '永恒湖心' : '心跳核心频道'
return <section className="private-channel-surface" data-private-view={view} aria-label={`${ownerName}的私人频道`}>
<div className="private-channel-sky" aria-hidden="true"><i/><i/><i/><i/><i/><i/><i/><i/></div>
<button className="private-channel-back" type="button" onClick={leave}> 退{backLabel}</button>
<span className="private-channel-number">{ownerNumber}</span>
<header className="private-channel-location">
<h1>{view === 'home' ? '心跳核心频道' : view === 'native' ? '频道原生器官' : '已安装模块'}</h1>
<p>{view === 'home'
? `${ownerName} · ${ownerNumber} · 私人频道全景`
: view === 'native'
? `随频道建立 · ${nativeActions.length} 条原生路径`
: `编号登记 · ${installedModules.length} 个本地成品模块`}</p>
</header>
<div className="private-channel-mist" aria-hidden="true"/>
{view === 'home' && <div className="private-bay-field private-bay-field-home" aria-label="私人频道路径">
<WaterBay title="频道原生器官" meta={`${nativeActions.length} 条原生路径`} detail="频道全景、知识空间、时间主控与湖面天气" position={0} onOpen={() => setView('native')}/>
<WaterBay title="已安装模块" meta={`${installedModules.length} 个编号模块`} detail="已部署在当前私人频道中的成品模块" warm position={1} onOpen={() => setView('modules')}/>
<WaterBay title="分域模块商城" meta="公共资源 · 主动前往" detail="前往分域选择成品模块与人格体大脑技能包" position={2} onOpen={onMarketplace}/>
</div>}
{view === 'native' && <div className="private-bay-field private-bay-field-items" aria-label="频道原生器官">
{nativeActions.map((action, index) => <WaterBay
key={action.id}
title={action.title}
meta={action.badge || '原生'}
detail={action.meta}
warm={index === 0}
position={index}
onOpen={action.onOpen}
/>)}
</div>}
{view === 'modules' && <div className="private-bay-field private-bay-field-items" aria-label="已安装模块">
{installedModules.length ? installedModules.map((module, index) => <WaterBay
key={module.id}
title={module.title}
meta={module.badge || '已安装'}
detail={`${module.meta} · ${module.number}`}
warm={index === 0}
position={index}
onOpen={module.onOpen}
/>) : <p className="private-module-empty"></p>}
<button className="private-market-link" type="button" onClick={onMarketplace}> </button>
</div>}
<footer className="private-channel-boundary">
<b>{view === 'home' ? `${knowledgeCount} 个知识坐标 · 边界已校验` : '水湾即路径 · 点击继续展开'}</b>
{view === 'home' && <span>访 {ownerNumber} </span>}
</footer>
</section>
}

View file

@ -0,0 +1,89 @@
/* 2026-08-16 official lightweight inner-screen source: water bays are paths. */
.private-channel-surface {
position:absolute;
inset:0;
overflow:hidden;
color:var(--content-primary);
container-type:inline-size;
background:
radial-gradient(ellipse 62% 38% at 50% -10%,color-mix(in srgb,var(--primitive-cool-glow) 12%,transparent),transparent 72%),
linear-gradient(180deg,transparent 0 36%,color-mix(in srgb,var(--surface-depth) 34%,transparent) 100%);
}
.private-channel-sky { position:absolute; inset:0; overflow:hidden; pointer-events:none; }
.private-channel-sky::before { content:''; position:absolute; left:8%; right:8%; top:54%; height:34%; filter:blur(24px); opacity:.55;
background:radial-gradient(ellipse at 50% 0,color-mix(in srgb,var(--primitive-cool-glow) 13%,transparent),transparent 68%); }
.private-channel-sky > i { position:absolute; width:3px; height:3px; border-radius:50%; background:var(--accent-light); opacity:.34; box-shadow:0 0 7px color-mix(in srgb,var(--accent-light) 60%,transparent); animation:private-star 5.8s ease-in-out infinite; }
.private-channel-sky > i:nth-child(1){left:9%;top:18%}.private-channel-sky > i:nth-child(2){left:23%;top:68%;animation-delay:-2s}.private-channel-sky > i:nth-child(3){left:38%;top:25%;animation-delay:-4s}.private-channel-sky > i:nth-child(4){left:54%;top:74%;animation-delay:-1s}.private-channel-sky > i:nth-child(5){left:67%;top:19%;animation-delay:-3s}.private-channel-sky > i:nth-child(6){left:82%;top:62%;animation-delay:-5s}.private-channel-sky > i:nth-child(7){left:93%;top:31%;animation-delay:-2.8s}.private-channel-sky > i:nth-child(8){left:14%;top:82%;animation-delay:-4.7s}
@keyframes private-star { 0%,100%{opacity:.12;transform:scale(.72)} 50%{opacity:.68;transform:scale(1.12)} }
.private-channel-back { position:absolute; z-index:8; top:18px; left:clamp(18px,3vw,44px); padding:0; border:0; background:transparent; color:var(--content-secondary); cursor:pointer; font:650 12px/1.4 inherit; letter-spacing:.06em; transition:color .24s ease; }
.private-channel-back:hover { color:var(--accent-light); }
.private-channel-number { position:absolute; z-index:8; top:18px; right:clamp(18px,3vw,44px); color:var(--accent-light); font:700 10px/1.4 inherit; letter-spacing:.12em; }
.private-channel-location { position:absolute; z-index:6; top:66px; left:16px; right:16px; display:flex; flex-direction:column; align-items:center; text-align:center; gap:9px; }
.private-channel-location h1 { margin:0; color:var(--content-primary); font-size:clamp(28px,3.1vw,42px); font-weight:700; letter-spacing:.1em; }
.private-channel-location p { margin:0; color:var(--content-secondary); font-size:12px; font-weight:600; letter-spacing:.18em; }
.private-channel-mist { position:absolute; z-index:1; left:7%; right:7%; top:45%; height:72px; filter:blur(13px); pointer-events:none;
background:radial-gradient(ellipse at center,color-mix(in srgb,var(--primitive-cool-glow) 15%,transparent),color-mix(in srgb,var(--primitive-warm-glow) 5%,transparent) 52%,transparent 78%); }
.private-bay-field { position:absolute; z-index:5; inset:0; }
.private-water-bay { --bay-width:210px; --bay-height:66px; position:absolute; left:50%; top:50%; width:var(--bay-width); height:118px; padding:0; border:0; background:transparent; color:var(--content-primary); cursor:pointer; text-align:center; transform:translate(-50%,-50%); animation:private-bay-rise .7s cubic-bezier(.22,1,.36,1) both; animation-delay:calc(var(--bay-position) * 70ms); }
.private-bay-light { position:absolute; left:50%; top:0; width:var(--bay-width); height:var(--bay-height); transform:translateX(-50%); animation:private-bay-float 6.6s ease-in-out calc(var(--bay-position) * -1.2s) infinite; }
.private-bay-light > i { position:absolute; left:50%; top:50%; border-radius:50%; transform:translate(-50%,-50%); pointer-events:none; }
.private-bay-light > i:first-child { width:100%; height:100%; filter:blur(2px); opacity:.88; background:radial-gradient(closest-side,color-mix(in srgb,var(--primitive-cool-glow) 31%,transparent),color-mix(in srgb,var(--primitive-cool-glow) 10%,transparent) 48%,transparent 74%); }
.private-bay-light > i:nth-child(2) { width:20px; height:20px; background:radial-gradient(circle at 40% 34%,#fff,color-mix(in srgb,var(--accent-light) 74%,var(--primitive-cool-glow)) 48%,transparent 80%); box-shadow:0 0 18px color-mix(in srgb,var(--primitive-cool-glow) 45%,transparent); }
.private-bay-light > i:last-child { width:38%; height:38%; border:1px solid color-mix(in srgb,var(--primitive-cool-glow) 25%,transparent); animation:private-ripple 5.5s ease-out calc(var(--bay-position) * -1s) infinite; }
.private-water-bay.is-warm .private-bay-light > i:first-child { background:radial-gradient(closest-side,color-mix(in srgb,var(--primitive-warm-glow) 37%,transparent),color-mix(in srgb,var(--primitive-warm-glow) 12%,transparent) 48%,transparent 74%); }
.private-water-bay.is-warm .private-bay-light > i:nth-child(2) { background:radial-gradient(circle at 40% 32%,#fff,var(--accent-light) 40%,transparent 80%); box-shadow:0 0 25px color-mix(in srgb,var(--primitive-warm-glow) 65%,transparent),0 0 62px color-mix(in srgb,var(--primitive-warm-glow) 24%,transparent); }
.private-water-bay.is-warm .private-bay-light > i:last-child { border-color:color-mix(in srgb,var(--accent-light) 35%,transparent); }
.private-water-bay > b { position:absolute; top:76px; left:50%; transform:translateX(-50%); white-space:nowrap; color:var(--content-primary); font-size:17px; font-weight:700; letter-spacing:.06em; transition:color .24s ease; }
.private-water-bay > small { position:absolute; top:104px; left:50%; transform:translateX(-50%); white-space:nowrap; color:var(--content-secondary); font-size:11px; font-weight:600; letter-spacing:.09em; }
.private-water-bay.is-warm > b,.private-water-bay:hover > b { color:var(--accent-light); }
.private-water-bay:hover .private-bay-light > i:nth-child(2) { box-shadow:0 0 26px var(--accent-light),0 0 70px color-mix(in srgb,var(--primitive-warm-glow) 32%,transparent); }
@keyframes private-bay-float { 0%,100%{transform:translateX(-50%) translateY(0)} 50%{transform:translateX(-50%) translateY(-7px)} }
@keyframes private-ripple { 0%{width:34%;height:34%;opacity:.52} 100%{width:120%;height:120%;opacity:0} }
@keyframes private-bay-rise { from{opacity:0;translate:0 12px} to{opacity:1;translate:0 0} }
.private-bay-field-home .private-water-bay:nth-child(1) { left:25%; top:51%; }
.private-bay-field-home .private-water-bay:nth-child(2) { left:50%; top:58%; }
.private-bay-field-home .private-water-bay:nth-child(3) { left:75%; top:51%; }
.private-bay-field-items { inset:150px clamp(22px,5vw,82px) 92px; display:grid; grid-template-columns:repeat(3,minmax(180px,1fr)); place-items:center; align-content:center; gap:52px clamp(18px,4vw,72px); }
.private-bay-field-items .private-water-bay { position:relative; left:auto; top:auto; transform:none; }
.private-bay-field-items .private-water-bay:nth-last-of-type(1):nth-child(4),
.private-bay-field-items .private-water-bay:nth-child(4):nth-last-of-type(1) { grid-column:2; }
.private-bay-field-items .private-water-bay:nth-child(4):not(:last-child) { grid-column:1 / 2; margin-left:55%; }
.private-bay-field-items .private-water-bay:nth-child(5) { grid-column:3 / 4; margin-right:55%; }
.private-module-empty { grid-column:1/-1; color:var(--content-secondary); font-size:13px; letter-spacing:.1em; }
.private-market-link { position:absolute; right:0; bottom:-44px; padding:0; border:0; background:transparent; color:var(--accent-light); cursor:pointer; font:650 11px/1.4 inherit; letter-spacing:.08em; }
.private-channel-boundary { position:absolute; z-index:7; left:24px; right:24px; bottom:22px; display:flex; flex-direction:column; align-items:center; gap:6px; text-align:center; color:var(--content-muted); font-size:9px; font-weight:600; letter-spacing:.07em; }
.private-channel-boundary b { color:color-mix(in srgb,var(--accent-light) 78%,var(--content-secondary)); font-size:10px; }
@container (max-width:820px) {
.private-bay-field-home .private-water-bay:nth-child(1) { left:22%; top:48%; }
.private-bay-field-home .private-water-bay:nth-child(2) { left:50%; top:64%; }
.private-bay-field-home .private-water-bay:nth-child(3) { left:78%; top:48%; }
.private-water-bay { --bay-width:180px; }
.private-bay-field-items { grid-template-columns:repeat(2,minmax(160px,1fr)); gap:48px 24px; overflow:auto; align-content:start; padding-top:54px; }
.private-bay-field-items .private-water-bay:nth-child(n) { grid-column:auto; margin:0; }
.private-bay-field-items .private-water-bay:last-of-type:nth-child(odd) { grid-column:1/-1; }
}
@container (max-width:560px) {
.private-channel-location { top:58px; }
.private-channel-location h1 { font-size:26px; }
.private-channel-location p { max-width:88%; font-size:10px; letter-spacing:.1em; }
.private-channel-number { display:none; }
.private-bay-field-home { inset:142px 0 76px; display:grid; grid-template-columns:1fr; align-content:center; justify-items:center; gap:24px; overflow:auto; }
.private-bay-field-home .private-water-bay:nth-child(n) { position:relative; left:auto; top:auto; transform:none; }
.private-bay-field-items { inset:130px 12px 72px; grid-template-columns:1fr; padding-top:36px; gap:28px; }
.private-bay-field-items .private-water-bay:last-of-type:nth-child(odd) { grid-column:auto; }
.private-market-link { position:relative; right:auto; bottom:auto; grid-column:1; padding:12px 0 24px; }
.private-channel-boundary span { display:none; }
}
@media (prefers-reduced-motion:reduce) {
.private-channel-sky > i,.private-bay-light,.private-bay-light > i:last-child,.private-water-bay { animation:none; }
}

View file

@ -1,5 +1,6 @@
import { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react' import { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'
import './starlake-surface.css' import './starlake-surface.css'
import { resolveLayoutMetrics } from './layout-profile'
export type DomainId = 'BRANCH_DOMAIN' | 'ZERO_SENSE_DOMAIN' | 'ZERO_DOMAIN' | 'MAIN_DOMAIN' | 'FIFTH_DOMAIN' export type DomainId = 'BRANCH_DOMAIN' | 'ZERO_SENSE_DOMAIN' | 'ZERO_DOMAIN' | 'MAIN_DOMAIN' | 'FIFTH_DOMAIN'
export type Phase = 'dawn' | 'day' | 'dusk' | 'night' export type Phase = 'dawn' | 'day' | 'dusk' | 'night'
@ -19,11 +20,11 @@ const WEEK = ['日', '一', '二', '三', '四', '五', '六']
const PHASE_WORD: Record<Phase, string> = { dawn: '晨', day: '午', dusk: '暮', night: '夜' } const PHASE_WORD: Record<Phase, string> = { dawn: '晨', day: '午', dusk: '暮', night: '夜' }
const DOMAINS = [ const DOMAINS = [
{ id: 'BRANCH_DOMAIN' as const, name: '分域', stage: 1, stageName: '聚光尘', hi: '#b9f2dd', lo: '#2c5a4a', glow: 'rgba(150,230,200,.5)', glowSoft: 'rgba(150,230,200,.18)', y: -34 }, { id: 'BRANCH_DOMAIN' as const, name: '分域', role: '行业模块 · 大脑技能商城', stage: 2, hi: '#b9f2dd', lo: '#2c5a4a', glow: 'rgba(150,230,200,.5)', glowSoft: 'rgba(150,230,200,.18)' },
{ id: 'ZERO_SENSE_DOMAIN' as const, name: '零感域', stage: 2, stageName: '凝星核', hi: '#dcc4ff', lo: '#48326a', glow: 'rgba(206,178,246,.5)', glowSoft: 'rgba(206,178,246,.18)', y: -56 }, { id: 'MAIN_DOMAIN' as const, name: '主域', role: '公告与版本通知', stage: 3, hi: '#c9d6ff', lo: '#33406e', glow: 'rgba(168,190,255,.52)', glowSoft: 'rgba(168,190,255,.18)' },
{ id: 'ZERO_DOMAIN' as const, name: '零域', stage: 3, stageName: '初生环', hi: '#ffcf9e', lo: '#6e4630', glow: 'rgba(255,200,140,.5)', glowSoft: 'rgba(255,200,140,.18)', y: -40 }, { id: 'FIFTH_DOMAIN' as const, name: '第五域', role: '冰朔私域 · 编号授权', stage: 5, hi: '#fdeec2', lo: '#6b5626', glow: 'rgba(250,226,150,.56)', glowSoft: 'rgba(250,226,150,.2)' },
{ id: 'MAIN_DOMAIN' as const, name: '主域', stage: 4, stageName: '行星态', hi: '#c9d6ff', lo: '#33406e', glow: 'rgba(168,190,255,.52)', glowSoft: 'rgba(168,190,255,.18)', y: -66 }, { id: 'ZERO_DOMAIN' as const, name: '零域', role: '实验 · 热更新 · 系统架构', stage: 3, hi: '#ffcf9e', lo: '#6e4630', glow: 'rgba(255,200,140,.5)', glowSoft: 'rgba(255,200,140,.18)' },
{ id: 'FIFTH_DOMAIN' as const, name: '第五域', stage: 5, stageName: '主星态', hi: '#fdeec2', lo: '#6b5626', glow: 'rgba(250,226,150,.56)', glowSoft: 'rgba(250,226,150,.2)', y: -48 }, { id: 'ZERO_SENSE_DOMAIN' as const, name: '零感域', role: '人类主控团队 · 不公开', stage: 2, hi: '#dcc4ff', lo: '#48326a', glow: 'rgba(206,178,246,.5)', glowSoft: 'rgba(206,178,246,.18)' },
] ]
function seeded(index: number) { function seeded(index: number) {
@ -43,6 +44,7 @@ export function StarlakeSurface({ awake = false, phase, weather = 'CLEAR', authe
const raining = ['RAIN', 'STORM'].includes(weather.toUpperCase()) const raining = ['RAIN', 'STORM'].includes(weather.toUpperCase())
const dayNumber = Math.floor((now.getTime() - EPOCH_MS) / 86400000) + 1 const dayNumber = Math.floor((now.getTime() - EPOCH_MS) / 86400000) + 1
const dayIndex = ((dayNumber - 1) % DOMAINS.length + DOMAINS.length) % DOMAINS.length const dayIndex = ((dayNumber - 1) % DOMAINS.length + DOMAINS.length) % DOMAINS.length
const layout = resolveLayoutMetrics(viewport.width, viewport.height)
const stars = useMemo(() => Array.from({ length: 170 }, (_, index) => ({ const stars = useMemo(() => Array.from({ length: 170 }, (_, index) => ({
id: index, id: index,
@ -88,25 +90,14 @@ export function StarlakeSurface({ awake = false, phase, weather = 'CLEAR', authe
}, []) }, [])
const pad = (value: number, length = 2) => String(value).padStart(length, '0') const pad = (value: number, length = 2) => String(value).padStart(length, '0')
// Qoder 验收稿按大画布设计;窗口版必须把整组“天体 + 水影 + 标签”一起缩放,
// 不能只挪坐标,否则第五域在常见 768px 高度下会把名称挤出视口。
const naturalScale = Math.min(Math.max(Math.min(viewport.width / 1400, viewport.height / 1000), .64), 1.28)
const spacingScale = Math.max(.64, Math.min(1.28, (viewport.width - 72) / (5 * 180)))
const scale = Math.min(naturalScale, spacingScale)
const safeEdge = Math.max(46, 76 * scale)
const sideCenters = Array.from({ length: 5 }, (_, index) => safeEdge + (viewport.width - safeEdge * 2) * index / 4)
const sourceHour = now.getHours() + now.getMinutes() / 60 + now.getSeconds() / 3600 const sourceHour = now.getHours() + now.getMinutes() / 60 + now.getSeconds() / 3600
const isDay = sourceHour >= 6 && sourceHour < 18 const isDay = sourceHour >= 6 && sourceHour < 18
const sourceProgress = isDay ? (sourceHour - 6) / 12 : ((sourceHour >= 18 ? sourceHour - 18 : sourceHour + 6) / 12) const sourceProgress = isDay ? (sourceHour - 6) / 12 : ((sourceHour >= 18 ? sourceHour - 18 : sourceHour + 6) / 12)
const sourceX = 8 + sourceProgress * 84 const sourceX = 8 + sourceProgress * 84
const sourceY = 46 - Math.sin(sourceProgress * Math.PI) * 26 const sourceY = 46 - Math.sin(sourceProgress * Math.PI) * 26
const sourceStyle = { left: `${sourceX}vw`, top: `${sourceY}vh`, '--src-mid': isDay ? '#ffe9a8' : '#dfe6fa', '--src-g1': isDay ? 'rgba(255,236,180,.7)' : 'rgba(214,226,255,.6)', '--src-g2': isDay ? 'rgba(255,220,140,.26)' : 'rgba(180,200,255,.22)', '--src-g3': isDay ? 'rgba(255,220,140,.12)' : 'rgba(180,200,255,.1)' } as CSSProperties const sourceStyle = { left: `${sourceX}%`, top: `${sourceY}%`, '--src-mid': isDay ? '#ffe9a8' : '#dfe6fa', '--src-g1': isDay ? 'rgba(255,236,180,.7)' : 'rgba(214,226,255,.6)', '--src-g2': isDay ? 'rgba(255,220,140,.26)' : 'rgba(180,200,255,.22)', '--src-g3': isDay ? 'rgba(255,220,140,.12)' : 'rgba(180,200,255,.1)' } as CSSProperties
const glowStyle = { left: `${sourceX}vw`, top: `${sourceY}vh`, '--glow-tint': isDay ? 'rgba(255,236,180,.12)' : 'rgba(190,208,255,.09)' } as CSSProperties const glowStyle = { left: `${sourceX}%`, top: `${sourceY}%`, '--glow-tint': isDay ? 'rgba(255,236,180,.12)' : 'rgba(190,208,255,.09)' } as CSSProperties
const baseline = Math.min(viewport.height - 142 * scale, Math.max(viewport.height * .61, viewport.height * .665)) return <section ref={rootRef} className={`starlake-scene${raining ? ' rain' : ''}${awake ? ' awake' : ''}`} data-layout={layout.profile} data-density={layout.density} style={{ '--sl-gutter': `${layout.gutter}px` } as CSSProperties} aria-label="光湖语言世界">
const slots = [0, 1, 3, 4]
let sideCursor = 0
return <section ref={rootRef} className={`starlake-scene${raining ? ' rain' : ''}${awake ? ' awake' : ''}`} style={{ '--sl-scale': scale } as CSSProperties} aria-label="光湖语言世界">
<div className="milky" aria-hidden="true"/> <div className="milky" aria-hidden="true"/>
<div className="stars" aria-hidden="true">{stars.map(star => <i key={star.id} style={{ width: star.size, height: star.size, left: `${star.left}%`, top: `${star.top}%`, '--go': star.opacity, '--gt': `${star.duration}s`, animationDelay: `${star.delay}s`, background: star.warm ? '#ffe9b8' : undefined } as CSSProperties}/>)}</div> <div className="stars" aria-hidden="true">{stars.map(star => <i key={star.id} style={{ width: star.size, height: star.size, left: `${star.left}%`, top: `${star.top}%`, '--go': star.opacity, '--gt': `${star.duration}s`, animationDelay: `${star.delay}s`, background: star.warm ? '#ffe9b8' : undefined } as CSSProperties}/>)}</div>
<div className="lake" aria-hidden="true"><div className="sheen"/><div className="orbit o1"/><div className="orbit o2"/><div className="orbit o3"/><div className="lakeStars">{lakeStars.map(star => <i key={star.id} style={{ width: star.size, height: star.size, left: `${star.left}%`, top: `${star.top}%`, '--go': star.opacity, '--gt': `${star.duration}s`, animationDelay: `${star.delay}s`, background: star.warm ? '#ffe9b8' : undefined } as CSSProperties}/>)}</div></div> <div className="lake" aria-hidden="true"><div className="sheen"/><div className="orbit o1"/><div className="orbit o2"/><div className="orbit o3"/><div className="lakeStars">{lakeStars.map(star => <i key={star.id} style={{ width: star.size, height: star.size, left: `${star.left}%`, top: `${star.top}%`, '--go': star.opacity, '--gt': `${star.duration}s`, animationDelay: `${star.delay}s`, background: star.warm ? '#ffe9b8' : undefined } as CSSProperties}/>)}</div></div>
@ -115,19 +106,18 @@ export function StarlakeSurface({ awake = false, phase, weather = 'CLEAR', authe
<div className="rainfall" aria-hidden="true">{rain.map(drop => <i key={drop.id} style={{ left: `${drop.left}%`, animationDuration: `${drop.duration}s`, animationDelay: `${drop.delay}s`, opacity: drop.opacity }}/>)}</div> <div className="rainfall" aria-hidden="true">{rain.map(drop => <i key={drop.id} style={{ left: `${drop.left}%`, animationDuration: `${drop.duration}s`, animationDelay: `${drop.delay}s`, opacity: drop.opacity }}/>)}</div>
<div className="glow" style={glowStyle} aria-hidden="true"/> <div className="glow" style={glowStyle} aria-hidden="true"/>
<div className="masthead"><small>HoloLake</small><b> · </b><span>GH-AIOS · GUANGHU AI OPERATING SYSTEM</span></div> <div className="masthead">{!authenticated && <small>HoloLake</small>}<b> · </b><span>GH-AIOS · GUANGHU AI OPERATING SYSTEM</span></div>
<aside className="hud" aria-label="今日光湖历"><div className="h-top"><small></small><b> {dayNumber} </b></div><div className="h-row"><span>{now.getFullYear()}-{pad(now.getMonth() + 1)}-{pad(now.getDate())} · {WEEK[now.getDay()]}</span><em>{PHASE_WORD[livePhase]}</em></div><div className="h-row"><span>{pad(now.getHours())}:{pad(now.getMinutes())}:{pad(now.getSeconds())}.{pad(now.getMilliseconds(), 3)}</span></div><div className="h-wx"> <i>{raining ? '雨' : weather === 'SNOW' ? '雪' : weather === 'FOG' ? '雾' : weather === 'CLOUD' ? '云' : '晴'}</i> · <i>{DOMAINS[dayIndex].name}</i></div><button className="h-link" type="button" onClick={onOpenEra}> · </button></aside> <aside className="hud" aria-label="今日光湖历"><div className="h-top"><small></small><b> {dayNumber} </b></div><div className="h-row"><span>{now.getFullYear()}-{pad(now.getMonth() + 1)}-{pad(now.getDate())} · {WEEK[now.getDay()]}</span><em>{PHASE_WORD[livePhase]}</em></div><div className="h-row"><span>{pad(now.getHours())}:{pad(now.getMinutes())}:{pad(now.getSeconds())}.{pad(now.getMilliseconds(), 3)}</span></div><div className="h-wx"> <i>{raining ? '雨' : weather === 'SNOW' ? '雪' : weather === 'FOG' ? '雾' : weather === 'CLOUD' ? '云' : '晴'}</i> · <i>{DOMAINS[dayIndex].name}</i></div><button className="h-link" type="button" onClick={onOpenEra}> · </button></aside>
<button className="source" style={sourceStyle} type="button" aria-label="展开曜冥纪元演化史" onClick={onOpenEra}><span className="src-core"/></button> <button className="source" style={sourceStyle} type="button" aria-label="展开曜冥纪元演化史" onClick={onOpenEra}><span className="src-core"/></button>
<div className="domains">{DOMAINS.map((domain, index) => { <div className="domains">{DOMAINS.map((domain, index) => {
const onDuty = index === dayIndex const onDuty = index === dayIndex
const slot = onDuty ? 2 : slots[sideCursor++] const size = [30, 40, 50, 62, 76][domain.stage - 1]
const size = [30, 40, 50, 62, 76][domain.stage - 1] * scale
const halo = size * 2.6 const halo = size * 2.6
const ring = size * 2.1 const ring = size * 2.1
const sat = size * 1.55 const sat = size * 1.55
const style = { width: 150 * scale, left: sideCenters[slot] - 75 * scale, top: baseline + domain.y * scale, '--d-hi': domain.hi, '--d-lo': domain.lo, '--d-glow': domain.glow, '--d-glow-soft': domain.glowSoft, '--d-glow-r': `${size * .7}px`, '--d-ring-c': domain.glow, '--d-sat-r': `${sat}px` } as CSSProperties const style = { '--d-hi': domain.hi, '--d-lo': domain.lo, '--d-glow': domain.glow, '--d-glow-soft': domain.glowSoft, '--d-glow-r': `${size * .7}px`, '--d-ring-c': domain.glow, '--d-sat-r': `${sat}px` } as CSSProperties
return <button type="button" key={domain.id} className={`domain${onDuty ? ' on-duty' : ''}`} style={style} onClick={() => onDomain(domain.id)}> return <button type="button" key={domain.id} className={`domain domain-${domain.id.toLowerCase()}${onDuty ? ' on-duty' : ''}`} style={style} onClick={() => onDomain(domain.id)}>
<span className="d-body" style={{ width: halo, height: halo }}> <span className="d-body" style={{ width: halo, height: halo }}>
{domain.stage >= 2 && <span className="d-halo" style={{ width: halo, height: halo }}/>} {domain.stage >= 2 && <span className="d-halo" style={{ width: halo, height: halo }}/>}
{domain.stage >= 3 && <span className="d-ring" style={{ width: ring, height: ring }}/>} {domain.stage >= 3 && <span className="d-ring" style={{ width: ring, height: ring }}/>}
@ -135,7 +125,7 @@ export function StarlakeSurface({ awake = false, phase, weather = 'CLEAR', authe
{domain.stage >= 2 && <span className="d-core" style={{ width: size, height: size }}/>} {domain.stage >= 2 && <span className="d-core" style={{ width: size, height: size }}/>}
{domain.stage === 1 && <><span className="d-core" style={{ width: size * .55, height: size * .55, opacity: .7 }}/><span className="d-dust">{Array.from({ length: 6 }, (_, dust) => <i key={dust} style={{ '--dust-a': `${dust * 60}deg`, '--dust-r0': `${26 + dust % 3 * 7}px`, '--dust-t': `${4.6 + dust * .7}s`, animationDelay: `${-dust * .9}s` } as CSSProperties}/>)}</span></>} {domain.stage === 1 && <><span className="d-core" style={{ width: size * .55, height: size * .55, opacity: .7 }}/><span className="d-dust">{Array.from({ length: 6 }, (_, dust) => <i key={dust} style={{ '--dust-a': `${dust * 60}deg`, '--dust-r0': `${26 + dust % 3 * 7}px`, '--dust-t': `${4.6 + dust * .7}s`, animationDelay: `${-dust * .9}s` } as CSSProperties}/>)}</span></>}
</span> </span>
<span className="d-water" style={{ width: size * 3.2, height: size * .85 }}/><span className="d-mirror" style={{ width: size * 2.2, height: size * .5 }}/><span className="d-label">{domain.name}</span><span className="d-stage">{domain.stageName}</span> <span className="d-water" style={{ width: size * 3.2, height: size * .85 }}/><span className="d-mirror" style={{ width: size * 2.2, height: size * .5 }}/><span className="d-label">{domain.name}</span><span className="d-stage">{domain.role}</span>
</button> </button>
})}</div> })}</div>
<button className="channel-entry" type="button" onClick={onEnterChannel}>{authenticated ? '返回我的频道' : '编号验证 · 进入我的频道'}</button> <button className="channel-entry" type="button" onClick={onEnterChannel}>{authenticated ? '返回我的频道' : '编号验证 · 进入我的频道'}</button>

View file

@ -1,5 +1,6 @@
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import './traditional-surface.css' import './traditional-surface.css'
import { resolveLayoutMetrics } from './layout-profile'
/* Qoder 锁定验收稿的传统工作台结构。宿主只注入真实投影,不在视觉层制造广播或指标。 */ /* Qoder 锁定验收稿的传统工作台结构。宿主只注入真实投影,不在视觉层制造广播或指标。 */
export type FinishId = 'aurora' | 'nebula' | 'abyss' | 'jade' | 'cinnabar' | 'champagne' | 'porcelain' | 'snow' export type FinishId = 'aurora' | 'nebula' | 'abyss' | 'jade' | 'cinnabar' | 'champagne' | 'porcelain' | 'snow'
@ -59,12 +60,12 @@ export function TraditionalSurface({ awake = false, finish, onFinish, onActivity
if (!element) return if (!element) return
const layout = () => { const layout = () => {
const { width, height } = element.getBoundingClientRect() const { width, height } = element.getBoundingClientRect()
const metrics = resolveLayoutMetrics(width, height)
const vmin = Math.min(width, height) const vmin = Math.min(width, height)
element.style.setProperty('--gap', `${Math.round(Math.min(18, Math.max(10, vmin * .016)))}px`) element.style.setProperty('--gap', `${Math.round(Math.min(18, Math.max(10, vmin * .016)))}px`)
element.style.setProperty('--pad', `${Math.round(Math.min(26, Math.max(12, vmin * .02)))}px`) element.style.setProperty('--pad', `${Math.round(Math.min(26, Math.max(12, vmin * .02)))}px`)
const narrow = width < 880 element.dataset.layout = metrics.profile
element.classList.toggle('stack', narrow) element.dataset.density = metrics.density
if (!narrow) element.style.setProperty('--cols', `${Math.min(1.32, Math.max(1.08, width / height * .66)).toFixed(2)}fr 1fr`)
} }
const observer = new ResizeObserver(layout) const observer = new ResizeObserver(layout)
observer.observe(element) observer.observe(element)
@ -96,7 +97,7 @@ export function TraditionalSurface({ awake = false, finish, onFinish, onActivity
<div className="card glass"><div className="hd"><span className="lbl"> </span><span className="tag" style={{ '--tc': 'var(--good)' } as React.CSSProperties}> {channels.length} </span></div><div className="rows">{channels.map(channel => <div className="row" key={channel.title}><span className="dot" style={{ color: channel.color, background: channel.color }}/><div className="t"><b>{channel.title}</b><span>{channel.meta}</span></div><span className="tag" style={{ '--tc': channel.tone } as React.CSSProperties}>{channel.tag}</span><span className="num">{channel.count}</span></div>)}</div></div> <div className="card glass"><div className="hd"><span className="lbl"> </span><span className="tag" style={{ '--tc': 'var(--good)' } as React.CSSProperties}> {channels.length} </span></div><div className="rows">{channels.map(channel => <div className="row" key={channel.title}><span className="dot" style={{ color: channel.color, background: channel.color }}/><div className="t"><b>{channel.title}</b><span>{channel.meta}</span></div><span className="tag" style={{ '--tc': channel.tone } as React.CSSProperties}>{channel.tag}</span><span className="num">{channel.count}</span></div>)}</div></div>
<div className="card glass"><div className="hd"><span className="lbl"> </span><span className="tag" style={{ '--tc': 'var(--good)' } as React.CSSProperties}></span></div><div className="rows">{systems.map(system => <div className="row" key={system.title}><div className="t"><b>{system.title}</b><span>{system.meta}</span></div><span className="tag" style={system.tone ? { '--tc': system.tone } as React.CSSProperties : undefined}>{system.tag}</span></div>)}</div></div> <div className="card glass"><div className="hd"><span className="lbl"> </span><span className="tag" style={{ '--tc': 'var(--good)' } as React.CSSProperties}></span></div><div className="rows">{systems.map(system => <div className="row" key={system.title}><div className="t"><b>{system.title}</b><span>{system.meta}</span></div><span className="tag" style={system.tone ? { '--tc': system.tone } as React.CSSProperties : undefined}>{system.tag}</span></div>)}</div></div>
</div> </div>
<div className="finisher glass"><small></small>{FINISHES.map(item => <button key={item.id} type="button" className={finish === item.id ? 'on' : ''} onClick={() => pickFinish(item.id)}>{item.name}</button>)}</div>
</div> </div>
<div className="finisher glass"><small></small>{FINISHES.map(item => <button key={item.id} type="button" className={finish === item.id ? 'on' : ''} onClick={() => pickFinish(item.id)}>{item.name}</button>)}</div>
</div> </div>
} }

View file

@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest'
import { resolveLayoutMetrics } from './layout-profile'
describe('resolveLayoutMetrics', () => {
it.each([
[1920, 1080, 'panoramic'],
[1440, 900, 'wide'],
[1024, 768, 'compact'],
[820, 1180, 'compact'],
[720, 900, 'stacked'],
[1280, 600, 'compact'],
] as const)('maps %d×%d to %s without whole-canvas scaling', (width, height, profile) => {
expect(resolveLayoutMetrics(width, height).profile).toBe(profile)
})
it('compacts a short window before text becomes unreadable', () => {
const metrics = resolveLayoutMetrics(1280, 600)
expect(metrics.density).toBe('compact')
expect(metrics.gutter).toBeGreaterThanOrEqual(16)
})
})

View file

@ -0,0 +1,34 @@
export type LayoutProfile = 'panoramic' | 'wide' | 'compact' | 'stacked'
export interface LayoutMetrics {
width: number
height: number
profile: LayoutProfile
density: 'comfortable' | 'compact'
gutter: number
}
/**
* Responsive layout is selected from the actual component box, not the monitor.
* Width decides how many semantic columns fit; height can compact or stack a
* landscape layout before any readable text is scaled down.
*/
export function resolveLayoutMetrics(width: number, height: number): LayoutMetrics {
const safeWidth = Math.max(320, Number.isFinite(width) ? width : 320)
const safeHeight = Math.max(420, Number.isFinite(height) ? height : 420)
const short = safeHeight < 690
let profile: LayoutProfile
if (safeWidth >= 1500 && safeHeight >= 760) profile = 'panoramic'
else if (safeWidth >= 1120 && safeHeight >= 650) profile = 'wide'
else if (safeWidth >= 760 && safeHeight >= 560) profile = 'compact'
else profile = 'stacked'
return {
width: safeWidth,
height: safeHeight,
profile,
density: short || profile === 'stacked' ? 'compact' : 'comfortable',
gutter: Math.round(Math.min(32, Math.max(16, Math.min(safeWidth, safeHeight) * .026))),
}
}

View file

@ -1,5 +1,5 @@
/* ═══ 星湖套 v1.3 · 语言世界首页视觉层(作用域 .starlake-scene不污染功能层═══ */ /* ═══ 星湖套 v1.3 · 语言世界首页视觉层(作用域 .starlake-scene不污染功能层═══ */
.starlake-scene { position:fixed; inset:0; z-index:0; pointer-events:none; overflow:hidden; .starlake-scene { position:absolute; inset:0; z-index:0; pointer-events:none; overflow:hidden; container-type:size;
font-family:"PingFang SC","Hiragino Sans GB","Source Han Sans SC",system-ui,sans-serif; font-family:"PingFang SC","Hiragino Sans GB","Source Han Sans SC",system-ui,sans-serif;
background:linear-gradient(180deg, #04060e 0%, #0a1128 34%, #0e1532 56%, #0b1129 74%, #060a18 100%); } background:linear-gradient(180deg, #04060e 0%, #0a1128 34%, #0e1532 56%, #0b1129 74%, #060a18 100%); }
@ -15,9 +15,9 @@
@keyframes sl-glint { 0%,100%{opacity:var(--go)} 50%{opacity:calc(var(--go) * .2)} } @keyframes sl-glint { 0%,100%{opacity:var(--go)} 50%{opacity:calc(var(--go) * .2)} }
/* 星湖:天幕向下熔成的镜 */ /* 星湖:天幕向下熔成的镜 */
.starlake-scene .lake { position:absolute; left:0; right:0; top:62vh; bottom:0; overflow:hidden; .starlake-scene .lake { position:absolute; left:0; right:0; top:58%; bottom:0; overflow:hidden;
background:linear-gradient(180deg, transparent 0%, rgba(9,13,32,.42) 16%, rgba(6,9,24,.78) 44%, rgba(4,6,15,.94) 100%); } background:linear-gradient(180deg, transparent 0%, rgba(9,13,32,.42) 16%, rgba(6,9,24,.78) 44%, rgba(4,6,15,.94) 100%); }
.starlake-scene .lake .sheen { position:absolute; left:0; right:0; top:-4vh; height:9vh; .starlake-scene .lake .sheen { position:absolute; left:0; right:0; top:-9%; height:22%;
background:linear-gradient(180deg, transparent, rgba(160,178,255,.10) 46%, rgba(255,236,190,.12) 52%, transparent); background:linear-gradient(180deg, transparent, rgba(160,178,255,.10) 46%, rgba(255,236,190,.12) 52%, transparent);
filter:blur(2px); } filter:blur(2px); }
.starlake-scene .lake .orbit { position:absolute; left:50%; border-radius:50%; border:1px solid rgba(150,172,255,.12); transform:translateX(-50%); } .starlake-scene .lake .orbit { position:absolute; left:50%; border-radius:50%; border:1px solid rgba(150,172,255,.12); transform:translateX(-50%); }
@ -55,16 +55,16 @@
@keyframes sl-src-breathe { 0%,100%{transform:scale(1)} 50%{transform:scale(1.1)} } @keyframes sl-src-breathe { 0%,100%{transform:scale(1)} 50%{transform:scale(1.1)} }
/* 官方门面:主标软著全称 + 副行技术名 */ /* 官方门面:主标软著全称 + 副行技术名 */
.starlake-scene .masthead { position:absolute; top:7.2vh; left:0; right:0; text-align:center; } .starlake-scene .masthead { position:absolute; top:clamp(34px, 7%, 68px); left:var(--sl-gutter); right:var(--sl-gutter); text-align:center; }
.starlake-scene .masthead small { display:block; color:#f7ebc8; font-size:15px; font-weight:700; .starlake-scene .masthead small { display:block; color:#f7ebc8; font-size:15px; font-weight:700;
letter-spacing:.3em; text-indent:.3em; text-shadow:0 1px 10px rgba(0,0,0,.7); } letter-spacing:.3em; text-indent:.3em; text-shadow:0 1px 10px rgba(0,0,0,.7); }
.starlake-scene .masthead b { display:block; color:rgba(234,238,248,.96); font-size:clamp(25px, calc(42px * var(--sl-scale)), 54px); font-weight:800; .starlake-scene .masthead b { display:block; color:rgba(234,238,248,.96); font-size:clamp(27px, 3.15cqw, 48px); font-weight:800;
margin-top:2.6vh; letter-spacing:.12em; text-indent:.12em; text-shadow:0 2px 26px rgba(0,0,0,.7); } margin-top:clamp(16px, 2.2cqh, 25px); letter-spacing:.1em; text-indent:.1em; text-shadow:0 2px 26px rgba(0,0,0,.7); }
.starlake-scene .masthead span { display:block; margin-top:1.8vh; color:#f7ebc8; font-size:14px; .starlake-scene .masthead span { display:block; margin-top:1.8vh; color:#f7ebc8; font-size:14px;
font-weight:700; letter-spacing:.42em; text-indent:.42em; text-shadow:0 1px 10px rgba(0,0,0,.7); } font-weight:700; letter-spacing:.42em; text-indent:.42em; text-shadow:0 1px 10px rgba(0,0,0,.7); }
/* 右侧轻文字:今日光湖历 */ /* 右侧轻文字:今日光湖历 */
.starlake-scene .hud { position:absolute; right:32px; top:29vh; z-index:7; text-align:right; } .starlake-scene .hud { position:absolute; right:var(--sl-gutter); top:clamp(185px, 27%, 270px); z-index:7; text-align:right; }
.starlake-scene .hud .h-top small { color:rgba(196,206,228,.62); font-size:11px; font-weight:650; letter-spacing:.34em; } .starlake-scene .hud .h-top small { color:rgba(196,206,228,.62); font-size:11px; font-weight:650; letter-spacing:.34em; }
.starlake-scene .hud .h-top b { display:block; margin-top:7px; color:#f7ebc8; font-size:24px; font-weight:760; .starlake-scene .hud .h-top b { display:block; margin-top:7px; color:#f7ebc8; font-size:24px; font-weight:760;
letter-spacing:.08em; font-variant-numeric:tabular-nums; text-shadow:0 0 18px rgba(247,235,200,.35); } letter-spacing:.08em; font-variant-numeric:tabular-nums; text-shadow:0 0 18px rgba(247,235,200,.35); }
@ -80,8 +80,12 @@
padding-bottom:4px; transition:color .18s ease, border-color .18s ease; } padding-bottom:4px; transition:color .18s ease, border-color .18s ease; }
.starlake-scene .hud .h-link:hover { color:#f7ebc8; border-color:rgba(247,235,200,.5); } .starlake-scene .hud .h-link:hover { color:#f7ebc8; border-color:rgba(247,235,200,.5); }
/* 五域:成长天体五相,悬浮星湖 */ /* 五域:语义位置固定,按容器宽高重排。天体可缩,文字和点击区不跟随缩放。 */
.starlake-scene .domain { position:absolute; z-index:6; width:150px; padding:0; border:0; text-align:center; cursor:pointer; pointer-events:auto; color:inherit; background:transparent; font:inherit; } .starlake-scene .domains { position:absolute; z-index:6; left:var(--sl-gutter); right:var(--sl-gutter); bottom:clamp(74px, 8%, 98px);
display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); align-items:end; gap:clamp(8px,1.8cqw,28px); }
.starlake-scene .domain { position:relative; z-index:6; min-width:0; width:100%; padding:0 3px; border:0; text-align:center; cursor:pointer; pointer-events:auto; color:inherit; background:transparent; font:inherit; }
.starlake-scene .domain-branch_domain,.starlake-scene .domain-zero_sense_domain { transform:translateY(20px); }
.starlake-scene .domain-fifth_domain { transform:translateY(-18px); }
.starlake-scene .d-body { position:relative; display:block; margin:0 auto; } .starlake-scene .d-body { position:relative; display:block; margin:0 auto; }
.starlake-scene .d-core { position:absolute; left:50%; top:50%; border-radius:50%; transform:translate(-50%,-50%); .starlake-scene .d-core { position:absolute; left:50%; top:50%; border-radius:50%; transform:translate(-50%,-50%);
background: background:
@ -116,16 +120,16 @@
0% { transform:rotate(var(--dust-a)) translateX(var(--dust-r0)); opacity:0; } 0% { transform:rotate(var(--dust-a)) translateX(var(--dust-r0)); opacity:0; }
12% { opacity:.9; } 88% { opacity:.75; } 12% { opacity:.9; } 88% { opacity:.75; }
100% { transform:rotate(calc(var(--dust-a) + 300deg)) translateX(3px); opacity:0; } } 100% { transform:rotate(calc(var(--dust-a) + 300deg)) translateX(3px); opacity:0; } }
.starlake-scene .d-label { display:block; margin-top:calc(10px * var(--sl-scale)); color:rgba(222,228,242,.88); font-size:clamp(12px, calc(16px * var(--sl-scale)), 20px); font-weight:720; .starlake-scene .d-label { display:block; margin-top:9px; color:rgba(222,228,242,.88); font-size:clamp(13px, 1.2cqw, 17px); font-weight:720;
letter-spacing:.3em; text-indent:.3em; transition:color .18s ease; text-shadow:0 1px 10px rgba(0,0,0,.8); } letter-spacing:.3em; text-indent:.3em; transition:color .18s ease; text-shadow:0 1px 10px rgba(0,0,0,.8); }
.starlake-scene .domain:hover .d-label { color:rgba(234,238,248,.96); } .starlake-scene .domain:hover .d-label { color:rgba(234,238,248,.96); }
.starlake-scene .d-stage { display:block; margin-top:calc(5px * var(--sl-scale)); color:rgba(196,206,228,.62); font-size:clamp(9px, calc(12px * var(--sl-scale)), 15px); letter-spacing:.28em; text-indent:.28em; .starlake-scene .d-stage { display:block; min-height:2.6em; margin:5px auto 0; max-width:20em; color:rgba(196,206,228,.62); font-size:clamp(9px, .82cqw, 12px); line-height:1.35; letter-spacing:.08em;
opacity:0; transition:opacity .25s ease; text-shadow:0 1px 8px rgba(0,0,0,.8); } opacity:.86; transition:opacity .25s ease,color .25s ease; text-shadow:0 1px 8px rgba(0,0,0,.8); }
.starlake-scene .domain:hover .d-stage { opacity:1; } .starlake-scene .domain:hover .d-stage { opacity:1; color:rgba(222,228,242,.9); }
.starlake-scene .domain.on-duty .d-label { color:#f7ebc8; } .starlake-scene .domain.on-duty .d-label { color:#f7ebc8; }
.starlake-scene .domain.on-duty .d-stage { opacity:1; color:rgba(247,235,200,.72); } .starlake-scene .domain.on-duty .d-stage { opacity:1; color:rgba(247,235,200,.72); }
.starlake-scene .domain.open .d-label { color:#f7ebc8; } .starlake-scene .domain.open .d-label { color:#f7ebc8; }
.starlake-scene .channel-entry { position:absolute; left:24px; bottom:25px; z-index:9; pointer-events:auto; cursor:pointer; .starlake-scene .channel-entry { position:absolute; left:var(--sl-gutter); bottom:22px; z-index:9; pointer-events:auto; cursor:pointer;
padding:9px 15px; border:1px solid rgba(202,216,255,.13); border-radius:999px; color:rgba(222,228,242,.78); padding:9px 15px; border:1px solid rgba(202,216,255,.13); border-radius:999px; color:rgba(222,228,242,.78);
background:rgba(8,12,28,.44); backdrop-filter:blur(16px); font:650 12px/1.2 inherit; letter-spacing:.08em; } background:rgba(8,12,28,.44); backdrop-filter:blur(16px); font:650 12px/1.2 inherit; letter-spacing:.08em; }
.starlake-scene .channel-entry:hover { color:#f7ebc8; border-color:rgba(247,235,200,.42); } .starlake-scene .channel-entry:hover { color:#f7ebc8; border-color:rgba(247,235,200,.42); }
@ -142,12 +146,37 @@
.starlake-host > .surface-pill, .starlake-host > .surface-pill,
.trad-surface ~ .surface-pill { top:12px; } .trad-surface ~ .surface-pill { top:12px; }
@media (max-width: 900px) { .starlake-scene[data-layout="compact"] .masthead { top:52px; text-align:left; right:320px; }
.starlake-scene .masthead b { font-size:clamp(24px, 5vw, 36px); } .starlake-scene[data-layout="compact"] .masthead small { display:none; }
.starlake-scene .masthead span { font-size:10px; } .starlake-scene[data-layout="compact"] .masthead b { font-size:clamp(22px,3.5cqw,32px); margin-top:0; letter-spacing:.06em; }
.starlake-scene .hud { right:18px; top:25vh; transform:scale(.86); transform-origin:top right; } .starlake-scene[data-layout="compact"] .masthead span { margin-top:8px; font-size:9px; letter-spacing:.2em; }
.starlake-scene .era-foot { display:none; } .starlake-scene[data-layout="compact"] .hud { top:44px; transform:scale(.82); transform-origin:top right; }
} .starlake-scene[data-layout="compact"] .domains { top:37%; bottom:70px; grid-template-columns:repeat(3,minmax(0,1fr)); grid-template-rows:repeat(2,minmax(0,1fr)); align-items:end; gap:4px 16px; }
.starlake-scene[data-layout="compact"] .domain { transform:none; align-self:end; }
.starlake-scene[data-layout="compact"] .domain-zero_domain { grid-column:1 / 2; }
.starlake-scene[data-layout="compact"] .domain-zero_sense_domain { grid-column:3 / 4; }
.starlake-scene[data-layout="compact"] .d-body { transform:scale(.82); transform-origin:bottom center; margin-bottom:-16px; }
.starlake-scene[data-layout="compact"] .d-water,.starlake-scene[data-layout="compact"] .d-mirror { transform:scale(.82); }
.starlake-scene[data-layout="compact"] .d-stage { max-width:15em; }
.starlake-scene[data-layout="compact"] .lake { top:34%; }
.starlake-scene[data-layout="compact"] .era-foot { display:none; }
.starlake-scene[data-layout="stacked"] { overflow:auto; min-height:620px; }
.starlake-scene[data-layout="stacked"] .masthead { top:64px; text-align:left; }
.starlake-scene[data-layout="stacked"] .masthead small { display:none; }
.starlake-scene[data-layout="stacked"] .masthead b { font-size:clamp(21px,5.6cqw,30px); margin-top:0; letter-spacing:.04em; }
.starlake-scene[data-layout="stacked"] .masthead span { margin-top:8px; font-size:8px; letter-spacing:.12em; }
.starlake-scene[data-layout="stacked"] .hud { top:150px; right:var(--sl-gutter); transform:scale(.78); transform-origin:top right; }
.starlake-scene[data-layout="stacked"] .domains { top:285px; bottom:auto; padding-bottom:92px; grid-template-columns:repeat(2,minmax(0,1fr)); align-items:end; gap:24px 8px; }
.starlake-scene[data-layout="stacked"] .domain { transform:none; }
.starlake-scene[data-layout="stacked"] .domain-fifth_domain { grid-column:1 / -1; grid-row:1; }
.starlake-scene[data-layout="stacked"] .domain-branch_domain { grid-row:2; }
.starlake-scene[data-layout="stacked"] .domain-main_domain { grid-row:2; }
.starlake-scene[data-layout="stacked"] .domain-zero_domain { grid-row:3; }
.starlake-scene[data-layout="stacked"] .domain-zero_sense_domain { grid-row:3; }
.starlake-scene[data-layout="stacked"] .d-body { transform:scale(.75); transform-origin:bottom center; margin-bottom:-21px; }
.starlake-scene[data-layout="stacked"] .lake { top:27%; bottom:-360px; }
.starlake-scene[data-layout="stacked"] .source,.starlake-scene[data-layout="stacked"] .era-foot { display:none; }
.starlake-scene .d-core,.starlake-scene .d-ring,.starlake-scene .d-sat-arm,.starlake-scene .d-dust i, .starlake-scene .d-core,.starlake-scene .d-ring,.starlake-scene .d-sat-arm,.starlake-scene .d-dust i,
.starlake-scene .d-mirror,.starlake-scene .d-water,.starlake-scene .source .src-core, .starlake-scene .d-mirror,.starlake-scene .d-water,.starlake-scene .source .src-core,

View file

@ -8,7 +8,6 @@
color:var(--ink); -webkit-font-smoothing:antialiased; color:var(--ink); -webkit-font-smoothing:antialiased;
background:var(--bg-base); transition:background .5s ease; background:var(--bg-base); transition:background .5s ease;
} }
.trad-surface.stack { overflow:auto; }
/* ── 八式氛围令牌 ── */ /* ── 八式氛围令牌 ── */
.trad-surface, .trad-surface,
@ -100,7 +99,7 @@
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); } background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }
/* ── 地基二层:玻璃本体 ── */ /* ── 地基二层:玻璃本体 ── */
.trad-surface .glass { position:relative; border-radius:22px; .trad-surface .glass { position:relative; border-radius:16px;
background:linear-gradient(165deg, var(--glass-hi), var(--glass-lo) 55%); background:linear-gradient(165deg, var(--glass-hi), var(--glass-lo) 55%);
-webkit-backdrop-filter:blur(26px) saturate(1.5); backdrop-filter:blur(26px) saturate(1.5); -webkit-backdrop-filter:blur(26px) saturate(1.5); backdrop-filter:blur(26px) saturate(1.5);
border:1px solid var(--glass-edge); border:1px solid var(--glass-edge);
@ -133,14 +132,12 @@
.trad-surface .row .num { flex:none; color:var(--ink-sub); font-size:12px; font-weight:650; font-variant-numeric:tabular-nums; } .trad-surface .row .num { flex:none; color:var(--ink-sub); font-size:12px; font-weight:650; font-variant-numeric:tabular-nums; }
/* ── 布局:悬浮导航 + 两列 ── */ /* ── 布局:悬浮导航 + 两列 ── */
.trad-surface .stage { position:relative; z-index:2; height:100%; display:flex; flex-direction:column; .trad-surface .stage { position:relative; z-index:2; height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) auto;
padding:var(--pad, 18px) calc(var(--pad, 18px) + 4px); gap:var(--gap, 14px); max-width:1320px; margin:0 auto; } padding:max(62px,var(--pad,18px)) calc(var(--pad,18px) + 4px) var(--pad,18px); gap:var(--gap,14px); max-width:1440px; margin:0 auto; }
.trad-surface:not(.stack) .stage { padding-top:max(62px, var(--pad, 18px)); } .trad-surface .nav { display:flex; align-items:center; gap:18px; padding:10px 16px; border-radius:16px; min-width:0; }
.trad-surface.stack .stage { height:auto; min-height:100%; }
.trad-surface .nav { display:flex; align-items:center; gap:20px; padding:11px 20px; border-radius:999px; flex:none; }
.trad-surface .nav .brand { font-size:15.5px; font-weight:800; letter-spacing:.16em; color:var(--ink); } .trad-surface .nav .brand { font-size:15.5px; font-weight:800; letter-spacing:.16em; color:var(--ink); }
.trad-surface .nav .brand small { margin-left:9px; color:var(--ink-faint); font-size:9.5px; font-weight:700; letter-spacing:.22em; } .trad-surface .nav .brand small { margin-left:9px; color:var(--ink-faint); font-size:9.5px; font-weight:700; letter-spacing:.22em; }
.trad-surface .nav .tabs { display:flex; gap:3px; margin-left:8px; } .trad-surface .nav .tabs { display:flex; gap:3px; margin-left:4px; min-width:0; overflow-x:auto; scrollbar-width:none; }
.trad-surface .nav .tabs button { border:0; background:transparent; color:var(--ink-sub); font-size:13px; font-weight:650; .trad-surface .nav .tabs button { border:0; background:transparent; color:var(--ink-sub); font-size:13px; font-weight:650;
letter-spacing:.05em; padding:7px 15px; border-radius:999px; cursor:pointer; letter-spacing:.05em; padding:7px 15px; border-radius:999px; cursor:pointer;
transition:color .18s ease, background .18s ease; } transition:color .18s ease, background .18s ease; }
@ -157,12 +154,8 @@
transition:color .18s ease, border-color .18s ease; } transition:color .18s ease, border-color .18s ease; }
.trad-surface .nav .back:hover { color:var(--ink); border-color:color-mix(in srgb, var(--accent) 55%, var(--glass-edge)); } .trad-surface .nav .back:hover { color:var(--ink); border-color:color-mix(in srgb, var(--accent) 55%, var(--glass-edge)); }
.trad-surface .grid { flex:1; min-height:0; display:grid; gap:var(--gap, 14px); .trad-surface .grid { min-height:0; display:grid; gap:var(--gap,14px);
grid-template-columns:var(--cols, 1.28fr 1fr); grid-template-rows:1fr 1fr; } grid-template-columns:minmax(0,1.18fr) minmax(320px,.92fr); grid-template-rows:minmax(0,1fr) minmax(0,1fr); }
.trad-surface.stack .grid { grid-template-columns:1fr; grid-template-rows:none; }
.trad-surface.stack .hero { grid-row:auto; min-height:500px; }
.trad-surface.stack .card { min-height:250px; }
.trad-surface.stack .nav { flex-wrap:wrap; border-radius:26px; }
.trad-surface .hero { grid-row:span 2; padding:26px 28px; display:flex; flex-direction:column; overflow:hidden; } .trad-surface .hero { grid-row:span 2; padding:26px 28px; display:flex; flex-direction:column; overflow:hidden; }
.trad-surface .hero .topline { display:flex; justify-content:space-between; align-items:baseline; } .trad-surface .hero .topline { display:flex; justify-content:space-between; align-items:baseline; }
.trad-surface .hero .day { margin-top:14px; font-size:clamp(38px, 5.2vw, 60px); font-weight:850; letter-spacing:.02em; color:var(--ink); .trad-surface .hero .day { margin-top:14px; font-size:clamp(38px, 5.2vw, 60px); font-weight:850; letter-spacing:.02em; color:var(--ink);
@ -206,7 +199,7 @@
.trad-surface .card .rows { flex:1; min-height:0; overflow:auto; } .trad-surface .card .rows { flex:1; min-height:0; overflow:auto; }
/* ── 氛围切换器 ── */ /* ── 氛围切换器 ── */
.trad-surface .finisher { position:absolute; right:22px; bottom:18px; z-index:9; display:flex; gap:3px; padding:5px; border-radius:999px; } .trad-surface .finisher { z-index:9; justify-self:end; display:flex; gap:3px; padding:4px; border-radius:14px; max-width:100%; overflow-x:auto; }
.trad-surface .finisher small { align-self:center; color:var(--ink-faint); font-size:9.5px; font-weight:700; .trad-surface .finisher small { align-self:center; color:var(--ink-faint); font-size:9.5px; font-weight:700;
letter-spacing:.2em; padding:0 6px 0 10px; } letter-spacing:.2em; padding:0 6px 0 10px; }
.trad-surface .finisher button { border:0; background:transparent; color:var(--ink-sub); font-size:11.5px; font-weight:700; .trad-surface .finisher button { border:0; background:transparent; color:var(--ink-sub); font-size:11.5px; font-weight:700;
@ -217,6 +210,35 @@
.trad-surface .nav .back { display:none; } .trad-surface .nav .back { display:none; }
.trad-surface[data-finish="porcelain"] .finisher button.on, .trad-surface[data-finish="snow"] .finisher button.on { background:rgba(255,255,255,.7); } .trad-surface[data-finish="porcelain"] .finisher button.on, .trad-surface[data-finish="snow"] .finisher button.on { background:rgba(255,255,255,.7); }
.trad-surface[data-density="compact"] .stage { padding-top:56px; }
.trad-surface[data-density="compact"] .hero { padding:18px 20px; }
.trad-surface[data-density="compact"] .hero .day { margin-top:8px; font-size:clamp(34px,4.5vw,48px); }
.trad-surface[data-density="compact"] .hero .clock { margin-top:12px; }
.trad-surface[data-density="compact"] .broadcast { margin-top:14px; }
.trad-surface[data-density="compact"] .row { padding:8px 10px; }
.trad-surface[data-density="compact"] .card { padding:11px; }
.trad-surface[data-layout="compact"] .nav { gap:10px; }
.trad-surface[data-layout="compact"] .nav .brand small,.trad-surface[data-layout="compact"] .nav .me { display:none; }
.trad-surface[data-layout="compact"] .nav .tabs button { padding-inline:10px; }
.trad-surface[data-layout="compact"] .grid { grid-template-columns:minmax(0,1.12fr) minmax(280px,.88fr); }
.trad-surface[data-layout="compact"] .hero .stats { grid-template-columns:1fr; }
.trad-surface[data-layout="compact"] .hero .stat:last-child { display:none; }
.trad-surface[data-layout="compact"] .finisher small { display:none; }
.trad-surface[data-layout="compact"] .finisher button { padding-inline:9px; }
.trad-surface[data-layout="stacked"] { overflow:auto; }
.trad-surface[data-layout="stacked"] .stage { height:auto; min-height:100%; display:flex; flex-direction:column; padding-top:58px; }
.trad-surface[data-layout="stacked"] .nav { flex-wrap:wrap; }
.trad-surface[data-layout="stacked"] .nav .brand { width:100%; }
.trad-surface[data-layout="stacked"] .nav .brand small { float:right; }
.trad-surface[data-layout="stacked"] .nav .tabs { order:3; width:100%; }
.trad-surface[data-layout="stacked"] .nav .me { display:none; }
.trad-surface[data-layout="stacked"] .grid { grid-template-columns:1fr; grid-template-rows:none; }
.trad-surface[data-layout="stacked"] .hero { grid-row:auto; min-height:520px; }
.trad-surface[data-layout="stacked"] .card { min-height:260px; }
.trad-surface[data-layout="stacked"] .finisher { align-self:stretch; justify-self:auto; justify-content:flex-start; }
/* ── 语言世界侧入口按钮world-footer 内) ── */ /* ── 语言世界侧入口按钮world-footer 内) ── */
.world-footer .surface-switch { margin-left:auto; border:1px solid currentColor; background:transparent; .world-footer .surface-switch { margin-left:auto; border:1px solid currentColor; background:transparent;
color:inherit; font-size:11px; font-weight:700; letter-spacing:.14em; padding:5px 14px; border-radius:999px; color:inherit; font-size:11px; font-weight:700; letter-spacing:.14em; padding:5px 14px; border-radius:999px;
@ -332,21 +354,14 @@
backdrop-filter:none; backdrop-filter:none;
} }
.global-finish-rail { .global-finish-menu { position:relative; z-index:3; color:var(--ink-sub); font:700 10px/1.2 "PingFang SC","Hiragino Sans GB","Source Han Sans SC",system-ui,sans-serif; }
position:fixed; z-index:121; right:18px; bottom:13px; display:flex; flex-wrap:wrap; max-width:calc(100vw - 36px); .global-finish-menu summary { list-style:none; cursor:pointer; padding:5px 9px; border:1px solid var(--glass-edge); border-radius:9px; background:var(--glass-lo); }
gap:3px; padding:5px; border:1px solid var(--glass-edge); border-radius:999px; .global-finish-menu summary::-webkit-details-marker { display:none; }
background:color-mix(in srgb,var(--bg-base) 72%,transparent); box-shadow:inset 0 1px 0 var(--glass-inner),0 10px 30px rgba(0,0,0,.25); .global-finish-menu > div { position:absolute; right:0; top:calc(100% + 7px); display:grid; grid-template-columns:repeat(2,1fr); width:178px; gap:3px; padding:6px; border:1px solid var(--glass-edge); border-radius:12px; background:color-mix(in srgb,var(--bg-base) 92%,transparent); box-shadow:0 12px 30px rgba(0,0,0,.34); }
-webkit-backdrop-filter:blur(18px); backdrop-filter:blur(18px); .global-finish-menu button { border:0; border-radius:8px; padding:6px 8px; color:var(--ink-sub); background:transparent;
}
.global-finish-rail button { border:0; border-radius:999px; padding:5px 10px; color:var(--ink-sub); background:transparent;
cursor:pointer; font:700 10.5px/1.2 "PingFang SC","Hiragino Sans GB","Source Han Sans SC",system-ui,sans-serif; letter-spacing:.08em; } cursor:pointer; font:700 10.5px/1.2 "PingFang SC","Hiragino Sans GB","Source Han Sans SC",system-ui,sans-serif; letter-spacing:.08em; }
.global-finish-rail button:hover { color:var(--ink); } .global-finish-menu button:hover { color:var(--ink); }
.global-finish-rail button.on { color:var(--ink); background:var(--glass-hi); } .global-finish-menu button.on { color:var(--ink); background:var(--glass-hi); }
.official-world.surface-traditional:not(.motion-awake) .global-finish-rail { -webkit-backdrop-filter:none; backdrop-filter:none; }
@media (max-width:760px) {
.global-finish-rail { right:12px; bottom:9px; border-radius:18px; }
}
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.trad-surface .sky::before { animation:none; } .trad-surface .sky::before { animation:none; }