feat: close complete numbering and responsive fifth-domain UI
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
|
@ -25,13 +25,15 @@
|
|||
"unavailable_behavior": "BEIJING_TIME_ONLY_NO_FAKE_WEATHER"
|
||||
},
|
||||
"visual_lock": {
|
||||
"layout": "QODER_LOCKED_STARLAKE_AND_TRADITIONAL_SURFACES_RESPONSIVELY_ADAPTED",
|
||||
"layout": "QODER_VISUAL_LANGUAGE_SEMANTICALLY_REASSEMBLED_ON_RESPONSIVE_HOLOLAKE_SHELL",
|
||||
"language_world_themes": ["夜湖星光", "晨湖曦光", "星云紫夜", "烛畔暖湖", "清浅澄湖"],
|
||||
"traditional_finishes": ["曜夜", "星辉", "深海", "翡翠", "朱砂", "香槟", "瓷光", "雪霁"],
|
||||
"all_downstream_pages_inherit_active_surface_tokens": true,
|
||||
"ambient_motion_requires_recent_human_pointer_or_keyboard_activity": true,
|
||||
"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,
|
||||
"daily_sampling_domain_changes_open_domain": false,
|
||||
"real_city_exposed_in_ui": false,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"schema": "hololake.gls-executable-projections/v2",
|
||||
"source_commit": "d5b1111fcaccaccf025070e531631f2b3cbb00cd",
|
||||
"source_commit": "104a5d73162bdf4a529701e65898e2bc2863ea9e",
|
||||
"runtime_graph_rule": "ONLY_EXPLICIT_RUNTIME_REQUIRES_EDGES_ENTER_ACTIVATION_GRAPH",
|
||||
"projections": {
|
||||
"GLS-0250": {"stage":"P0","projection_kind":"TYPED_FACT_AND_DOMAIN_BOUNDARY","adapter":"origin-domain-topology","event_kinds":["BOOTSTRAP","DOMAIN_ROUTE"],"dependencies":[]},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"schema": "hololake.gls-native-runtime-kernel/v1",
|
||||
"record_id": "HLP-GLS-NATIVE-RUNTIME-KERNEL-001",
|
||||
"source_commit": "d5b1111fcaccaccf025070e531631f2b3cbb00cd",
|
||||
"source_commit": "104a5d73162bdf4a529701e65898e2bc2863ea9e",
|
||||
"receipt_schema": "hololake.protocol-decision-receipt/v1",
|
||||
"decision_set": ["ALLOW", "DENY", "AMBIGUOUS", "UNVERIFIED"],
|
||||
"runtime_boundaries": {
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
]
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
],
|
||||
"trust_registry": {
|
||||
"repository": "REPO-012",
|
||||
"source_commit": "d5b1111fcaccaccf025070e531631f2b3cbb00cd",
|
||||
"source_commit": "104a5d73162bdf4a529701e65898e2bc2863ea9e",
|
||||
"source_path": "routing/persona-control-authorization-signers.json",
|
||||
"source_sha256": "35e7ebac46034e9df5cf61d4dccfee2e624331c1daf08251acb07c3d626974ef",
|
||||
"registry_id": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"record_id": "HLP-ZERO-CORE-NUMBERING-KERNEL-001",
|
||||
"authority": {
|
||||
"repository": "REPO-012",
|
||||
"source_commit": "d5b1111fcaccaccf025070e531631f2b3cbb00cd",
|
||||
"source_commit": "104a5d73162bdf4a529701e65898e2bc2863ea9e",
|
||||
"source_path": "routing/guanghu-identity-authority-map.json",
|
||||
"map_id": "GH-IDENTITY-AUTHORITY-MAP-001",
|
||||
"map_version": "2026-08-10.1",
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ const sourceRoot = explicitSourceRoot || (repoRoot ? path.join(repoRoot, 'gls')
|
|||
const sourceCommit = args.get('--source-commit')
|
||||
const output = args.get('--output')
|
||||
const projectionsPath = args.get('--projections')
|
||||
const referencesPath = args.get('--references')
|
||||
|
||||
if (!repoRoot || !sourceRoot || !sourceCommit || !output || !projectionsPath) {
|
||||
throw new Error('usage: compile-gls-runtime-registry.mjs --repo-root <REPO-012> --source-commit <sha> --output <manifest.json> --projections <projections.json>')
|
||||
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> --references <reference-nodes.json>')
|
||||
}
|
||||
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) {
|
||||
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 glsEntryPath = 'gls/GLS-ENTRY.hdlp'
|
||||
|
|
@ -222,29 +235,35 @@ for (const relative of await walk(routingRoot)) {
|
|||
}
|
||||
}
|
||||
|
||||
const candidates = (await walk(sourceRoot))
|
||||
.map((relative) => ({ relative, match: path.basename(relative).match(/^(GLS-\d{4}).*\.hdlp$/) }))
|
||||
const allSourceFiles = await walk(sourceRoot)
|
||||
const candidates = allSourceFiles
|
||||
.map((relative) => ({ relative, match: path.basename(relative).match(/^(GLS-\d{4})(?:[^0-9].*)?\.(hdlp|md)$/) }))
|
||||
.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()
|
||||
for (const candidate of candidates) {
|
||||
const group = grouped.get(candidate.id) || []
|
||||
group.push(candidate.relative)
|
||||
group.push(candidate)
|
||||
grouped.set(candidate.id, group)
|
||||
}
|
||||
|
||||
const selectedSource = new Map()
|
||||
for (const [id, sources] of grouped) {
|
||||
sources.sort((left, right) => sourceRank(left) - sourceRank(right) || left.localeCompare(right))
|
||||
const relative = sources[0]
|
||||
const protocolSources = sources.filter((source) => source.source_format === 'HDLP_PROTOCOL_SOURCE')
|
||||
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, {
|
||||
relative,
|
||||
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 draftIds = new Set((protocolRegistry.registered_draft_protocols || []).map((entry) => entry.id))
|
||||
const legacyDependencyTargets = new Set()
|
||||
|
|
@ -256,6 +275,56 @@ for (const id of draftIds) {
|
|||
}
|
||||
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 = []
|
||||
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))
|
||||
|
|
@ -280,6 +349,8 @@ for (const [id, source] of [...selectedSource.entries()].sort(([left], [right])
|
|||
enters_runtime_graph: false,
|
||||
target_registered: protocolRegistryIds.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) => ({
|
||||
target,
|
||||
|
|
@ -288,18 +359,21 @@ for (const [id, source] of [...selectedSource.entries()].sort(([left], [right])
|
|||
enters_runtime_graph: true,
|
||||
target_registered: protocolRegistryIds.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 = []
|
||||
if (!projection) activationBlockers.push('NO_EXECUTABLE_ADAPTER')
|
||||
if (registrationState === 'DISCOVERED_UNRECONCILED') activationBlockers.push('REGISTRATION_NOT_RECONCILED')
|
||||
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({
|
||||
id,
|
||||
title: cleanHeading(heading || id, id),
|
||||
source_status: sourceStatus(source.raw),
|
||||
source_path: `gls/${source.relative}`,
|
||||
source_format: source.source_format,
|
||||
source_sha256: createHash('sha256').update(source.raw).digest('hex'),
|
||||
alternate_source_count: source.alternate_source_count,
|
||||
registration: {
|
||||
|
|
@ -354,6 +428,7 @@ for (const protocol of protocols) {
|
|||
}
|
||||
const dependenciesNotInRegistry = [...legacyDependencyTargets].filter((id) => !protocolRegistryIds.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 registry = {
|
||||
schema: 'hololake.gls-runtime-manifest/v2',
|
||||
|
|
@ -384,6 +459,10 @@ const registry = {
|
|||
legacy_dependency_target_count: legacyDependencyTargets.size,
|
||||
dependencies_not_in_protocol_registry: dependenciesNotInRegistry,
|
||||
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,
|
||||
legacy_dependency_cycles: legacyDependencyCycles,
|
||||
source_reference_cycles: legacyDependencyCycles,
|
||||
|
|
@ -395,8 +474,10 @@ const registry = {
|
|||
protocol_count: protocols.length,
|
||||
executable_projection_count: executableCount,
|
||||
inventoried_not_executable_count: protocols.length - executableCount,
|
||||
number_coordinate_count: protocols.length + numberedReferenceNodes.length,
|
||||
numbered_reference_nodes: numberedReferenceNodes,
|
||||
protocols,
|
||||
}
|
||||
|
||||
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}`)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const paths = {
|
|||
identity: resolve(root, 'contracts/zero-core-numbering-kernel.json'),
|
||||
webview: resolve(root, 'contracts/numbered-ipc-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'),
|
||||
}
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ export function compileUnifiedNumberTree() {
|
|||
const identity = readSource(paths.identity)
|
||||
const webview = readSource(paths.webview)
|
||||
const broker = readSource(paths.broker)
|
||||
const gls = readSource(paths.gls)
|
||||
const routes = [
|
||||
...webview.value.operations.map((route) => ({
|
||||
transport: 'TAURI_WEBVIEW_NUMBERED_IPC',
|
||||
|
|
@ -64,8 +66,48 @@ export function compileUnifiedNumberTree() {
|
|||
if (routes.length !== uniquePaths.size || routes.length !== uniqueOperations.size) {
|
||||
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 {
|
||||
schema: 'hololake.unified-number-coordinate-tree/v1',
|
||||
schema: 'hololake.unified-number-coordinate-tree/v2',
|
||||
recordId: 'HLP-UNIFIED-NUMBER-TREE-001',
|
||||
state: 'MACHINE_COMPILED_STARTUP_ENFORCED',
|
||||
rootNumber: 'HLP-NUMBER-WORLD-ROOT-001',
|
||||
|
|
@ -84,6 +126,7 @@ export function compileUnifiedNumberTree() {
|
|||
{ recordId: identity.value.record_id, sha256: identity.sha256 },
|
||||
{ recordId: webview.value.record_id, sha256: webview.sha256 },
|
||||
{ recordId: broker.value.record_id, sha256: broker.sha256 },
|
||||
{ recordId: gls.value.record_id, sha256: gls.sha256 },
|
||||
],
|
||||
invariants: {
|
||||
numberIsStableCoordinateNotAuthority: true,
|
||||
|
|
@ -91,9 +134,18 @@ export function compileUnifiedNumberTree() {
|
|||
admissionIsSeparateFromIdentity: true,
|
||||
everyPhysicalCallHasNumberedRoute: true,
|
||||
everyAcceptedCallHasEvidenceClass: true,
|
||||
everyProtocolReferenceHasNumberCoordinate: true,
|
||||
referenceOnlyNodesNeverExecutable: true,
|
||||
unresolvedNumberReferenceCount: 0,
|
||||
mismatchedCoordinate: 'FAIL_CLOSED',
|
||||
},
|
||||
coordinateCount: routes.length + identityNodes.length + protocolNodes.length,
|
||||
routeCount: routes.length,
|
||||
identityNodeCount: identityNodes.length,
|
||||
protocolNodeCount: protocolNodes.length,
|
||||
referenceOnlyNodeCount: protocolNodes.filter((node) => node.nodeKind === 'GLS_REFERENCE_ONLY').length,
|
||||
identityNodes,
|
||||
protocolNodes,
|
||||
routes,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const starlakeStyles = read('src/modules/qoder-surface/starlake-surface.css')
|
|||
const traditional = read('src/modules/qoder-surface/TraditionalSurface.tsx')
|
||||
const traditionalStyles = read('src/modules/qoder-surface/traditional-surface.css')
|
||||
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', () => {
|
||||
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', () => {
|
||||
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.traditional_finishes.length, 8)
|
||||
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(styles, /\.world-climate-veil/)
|
||||
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(traditional, /broadcasts\.map/)
|
||||
assert.match(traditionalStyles, /\.official-world\.surface-traditional/)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import fs from 'node:fs'
|
|||
import test from 'node:test'
|
||||
|
||||
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', () => {
|
||||
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, /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', () => {
|
||||
|
|
@ -32,12 +36,22 @@ test('knowledge belongs to Heartbeat Core while persona repositories belong to L
|
|||
const toolStart = source.indexOf("worldStage === 'tool'", lightLakeStart)
|
||||
const heartbeat = source.slice(heartbeatStart, lightLakeStart)
|
||||
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.match(lightLake, /<h1>光之湖<\/h1>/)
|
||||
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', () => {
|
||||
assert.match(source, /'ICE-GL∞': 'bingshuo'/)
|
||||
assert.match(source, /'ICE-GL-ZHI∞': 'zhizhi'/)
|
||||
|
|
|
|||
|
|
@ -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.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.compiler.raw_protocol_text_executed, false)
|
||||
assert.equal(registry.compiler.arbitrary_protocol_code_allowed, false)
|
||||
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.protocol_count, 75)
|
||||
assert.equal(new Set(registry.protocols.map((protocol) => protocol.id)).size, 75)
|
||||
assert.equal(registry.protocol_count, 83)
|
||||
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.equal(registry.reconciliation.protocol_registry_id_count, 52)
|
||||
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.legacy_dependency_target_count, 57)
|
||||
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.numbered_sources_not_in_protocol_registry.length, 31)
|
||||
assert.equal(registry.reconciliation.dependencies_without_numbered_source.length, 16)
|
||||
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.discovered_unreconciled_count, 0)
|
||||
assert.equal(registry.reconciliation.authority_conflict_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(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 () => {
|
||||
|
|
@ -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-0003'], undefined)
|
||||
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 () => {
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
const generated = JSON.parse(readFileSync(new URL('../generated/unified-number-coordinate-tree.json', import.meta.url), 'utf8'))
|
||||
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.coordinateCount, 272)
|
||||
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.identityNodes, ...generated.protocolNodes, ...generated.routes].map((node) => node.path)).size, 272)
|
||||
assert.equal(generated.invariants.everyPhysicalCallHasNumberedRoute, 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))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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.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.unknown_number, 'FAIL_CLOSED')
|
||||
assert.equal(contract.runtime.automatic_identity_issuance, false)
|
||||
|
|
|
|||
|
|
@ -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 REQUEST_SCHEMA: &str = "hololake.protocol-decision-request/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;
|
||||
static LEDGER_SCHEMA_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::collections::{HashMap, HashSet};
|
|||
|
||||
const EMBEDDED_REGISTRY: &str = include_str!("../../contracts/gls-runtime-registry.json");
|
||||
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)]
|
||||
struct GlsRuntimeRegistry {
|
||||
|
|
@ -19,6 +19,8 @@ struct GlsRuntimeRegistry {
|
|||
protocol_count: usize,
|
||||
executable_projection_count: usize,
|
||||
inventoried_not_executable_count: usize,
|
||||
number_coordinate_count: usize,
|
||||
numbered_reference_nodes: Vec<GlsNumberedReferenceNode>,
|
||||
protocols: Vec<GlsProtocol>,
|
||||
}
|
||||
|
||||
|
|
@ -59,6 +61,10 @@ struct GlsReconciliation {
|
|||
legacy_dependency_target_count: usize,
|
||||
dependencies_not_in_protocol_registry: 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>,
|
||||
legacy_dependency_cycles: Vec<Vec<String>>,
|
||||
source_reference_cycles: Vec<Vec<String>>,
|
||||
|
|
@ -79,11 +85,24 @@ struct GlsDependencyEdge {
|
|||
edge_kind: String,
|
||||
enters_runtime_graph: bool,
|
||||
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)]
|
||||
struct GlsProtocol {
|
||||
id: String,
|
||||
source_format: String,
|
||||
source_sha256: String,
|
||||
registration: GlsRegistration,
|
||||
projection_state: String,
|
||||
|
|
@ -109,6 +128,10 @@ pub struct GlsProtocolRuntimeSnapshot {
|
|||
pub registered_draft_count: usize,
|
||||
pub registered_draft_not_started_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 legacy_dependency_cycle_count: usize,
|
||||
pub discovered_unreconciled_count: usize,
|
||||
|
|
@ -160,12 +183,21 @@ fn validate_registry(registry: &GlsRuntimeRegistry) -> Result<(), String> {
|
|||
.reconciliation
|
||||
.dependencies_without_numbered_source
|
||||
.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
|
||||
.reconciliation
|
||||
.numbered_sources_not_in_protocol_registry
|
||||
.len()
|
||||
!= 31
|
||||
!= 32
|
||||
|| registry.reconciliation.legacy_dependency_cycles.len() != 3
|
||||
|| registry.reconciliation.source_reference_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.authority_conflict_count != 0
|
||||
|| 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.executable_projection_count + registry.inventoried_not_executable_count
|
||||
{
|
||||
return Err("HOLOLAKE_GLS_RUNTIME_BOUNDARY_INVALID".into());
|
||||
}
|
||||
|
||||
let mut reference_ids = HashSet::new();
|
||||
let mut reference_numbers = HashSet::new();
|
||||
for reference in ®istry.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();
|
||||
for authority in ®istry.source.authority_files {
|
||||
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() {
|
||||
"EXECUTABLE_PROJECTION" => {
|
||||
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.activation_blockers.is_empty()
|
||||
{
|
||||
|
|
@ -217,7 +281,10 @@ fn validate_registry(registry: &GlsRuntimeRegistry) -> Result<(), String> {
|
|||
}
|
||||
}
|
||||
"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.dependencies.is_empty()
|
||||
{
|
||||
|
|
@ -233,6 +300,14 @@ fn validate_registry(registry: &GlsRuntimeRegistry) -> Result<(), String> {
|
|||
return Err("HOLOLAKE_GLS_REGISTRATION_NOT_RECONCILED".into());
|
||||
}
|
||||
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.classification_basis.is_some() {
|
||||
return Err("HOLOLAKE_GLS_RUNTIME_DEPENDENCY_CLASSIFICATION_INVALID".into());
|
||||
|
|
@ -278,7 +353,7 @@ fn validate_registry(registry: &GlsRuntimeRegistry) -> Result<(), String> {
|
|||
.collect::<HashSet<_>>();
|
||||
if stages != HashSet::from(["P0", "P1", "P2", "P3", "P4", "P5", "P6"])
|
||||
|| registry.executable_projection_count != 25
|
||||
|| registry.inventoried_not_executable_count != 50
|
||||
|| registry.inventoried_not_executable_count != 58
|
||||
|| registry
|
||||
.reconciliation
|
||||
.typed_source_dependency_counts
|
||||
|
|
@ -378,10 +453,16 @@ pub async fn get_gls_protocol_runtime() -> Result<GlsProtocolRuntimeSnapshot, St
|
|||
.reconciliation
|
||||
.registered_draft_not_started_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
|
||||
.dependencies_without_numbered_source
|
||||
.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(),
|
||||
discovered_unreconciled_count: registry.reconciliation.discovered_unreconciled_count,
|
||||
authority_conflict_count: registry.reconciliation.authority_conflict_count,
|
||||
|
|
@ -413,8 +494,12 @@ mod tests {
|
|||
#[test]
|
||||
fn registry_is_pinned_and_never_executes_raw_protocol_text() {
|
||||
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.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.legacy_dependency_target_count, 57);
|
||||
assert_eq!(registry.reconciliation.legacy_dependency_cycles.len(), 3);
|
||||
|
|
|
|||
|
|
@ -10,11 +10,43 @@ struct NumberTree {
|
|||
record_id: String,
|
||||
state: String,
|
||||
root_number: String,
|
||||
coordinate_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>,
|
||||
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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct NumberRoute {
|
||||
|
|
@ -39,6 +71,9 @@ struct NumberTreeInvariants {
|
|||
admission_is_separate_from_identity: bool,
|
||||
every_physical_call_has_numbered_route: 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,
|
||||
}
|
||||
|
||||
|
|
@ -49,23 +84,91 @@ pub(crate) fn start_on_application_open() -> Result<(), String> {
|
|||
fn validate_tree() -> Result<(), String> {
|
||||
let tree: NumberTree = serde_json::from_str(TREE)
|
||||
.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.state != "MACHINE_COMPILED_STARTUP_ENFORCED"
|
||||
|| tree.root_number != "HLP-NUMBER-WORLD-ROOT-001"
|
||||
|| tree.coordinate_count != 272
|
||||
|| tree.route_count != 169
|
||||
|| 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.path_is_unique_navigation
|
||||
|| !tree.invariants.admission_is_separate_from_identity
|
||||
|| !tree.invariants.every_physical_call_has_numbered_route
|
||||
|| !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"
|
||||
{
|
||||
return Err("HOLOLAKE_UNIFIED_NUMBER_TREE_BOUNDARY_INVALID".into());
|
||||
}
|
||||
let mut paths = 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 {
|
||||
let coordinate = format!(
|
||||
"{}:{}:{}:{}:{}:{}",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type { ChannelWorkbenchSnapshot } from './modules/channel-workbench'
|
|||
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 { 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 PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
|
||||
|
|
@ -132,6 +133,10 @@ interface GlsProtocolRuntimeSnapshot {
|
|||
registeredDraftCount: number
|
||||
registeredDraftNotStartedCount: number
|
||||
legacyDependencyTargetCount: number
|
||||
numberCoordinateCount: number
|
||||
numberedReferenceNodeCount: number
|
||||
independentSourceGapCount: number
|
||||
unresolvedNumberReferenceCount: number
|
||||
dependencyGapCount: number
|
||||
legacyDependencyCycleCount: 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 domainGates = [
|
||||
{ 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: [
|
||||
['域标识', '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: [
|
||||
['域标识', '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: [
|
||||
['域标识', '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: [
|
||||
['域标识', 'FIFTH_DOMAIN'], ['责任主体', '冰朔 · ICE-GL∞'], ['系统入口', '永恒湖心系统'], ['访问状态', 'PRIVATE · LIVE'],
|
||||
['授权边界', '私有自由部署 · 逆向访问须经 ICE-GL∞ 编号授权'], ['域标识', 'FIFTH_DOMAIN'], ['责任主体', '冰朔 · ICE-GL∞'], ['系统入口', '永恒湖心系统'], ['访问状态', 'PRIVATE · LIVE'],
|
||||
] },
|
||||
]
|
||||
const starPoints = [
|
||||
|
|
@ -1799,7 +1804,7 @@ function HoloLakeApp() {
|
|||
<button className="secondary-button" type="button" disabled={serverPnccBusy} onClick={() => void refreshServerPncc()}>{serverPnccBusy ? '正在读取…' : '重新读取主控状态'}</button>
|
||||
</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} · P0–P6 共 {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} · P0–P6 共 {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>P1–P6 原生器官</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">
|
||||
|
|
@ -1868,9 +1873,10 @@ function HoloLakeApp() {
|
|||
<button type="button" className={surface === 'world' ? 'on' : ''} onClick={() => chooseSurface('world')}>语言世界</button>
|
||||
<button type="button" className={surface === 'traditional' ? 'on' : ''} onClick={() => chooseSurface('traditional')}>传统工作台</button>
|
||||
</div>
|
||||
const finishRail = surface === 'traditional' && worldStage !== 'domain' ? <div className="global-finish-rail" 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> : null
|
||||
const activeFinishName = FINISHES.find((item) => item.id === traditionalFinish)?.name || '曜夜'
|
||||
const finishRail = surface === 'traditional' && worldStage !== 'domain' ? <details className="global-finish-menu">
|
||||
<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 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(() => {
|
||||
|
|
@ -1940,9 +1946,9 @@ function HoloLakeApp() {
|
|||
const resolvedDomain = zeroPoint?.resolvedDomain || ''
|
||||
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}>
|
||||
{surfacePill}{finishRail}
|
||||
{surfacePill}
|
||||
<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' : ''}`}>
|
||||
<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)}/>) }
|
||||
|
|
@ -2003,10 +2009,23 @@ function HoloLakeApp() {
|
|||
|
||||
const isZhizhi = repoLogin.domain === 'FIFTH_DOMAIN' && zeroPoint?.userNumber === 'ICE-GL-ZHI∞'
|
||||
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}>
|
||||
{surfacePill}{finishRail}
|
||||
{surfacePill}
|
||||
<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">
|
||||
{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">
|
||||
|
|
@ -2083,19 +2102,7 @@ function HoloLakeApp() {
|
|||
<footer><b>当前状态 · 待本人接入</b><span>可由本人人格体协助执行,但不能替代人类节点所有权确认。</span></footer>
|
||||
</div>
|
||||
</section>}
|
||||
{worldStage === 'heartbeat' && repoLogin.domain === 'FIFTH_DOMAIN' && <section className="channel-world heartbeat-world">
|
||||
<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 === '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}/>}
|
||||
{worldStage === 'lightLake' && repoLogin.domain === 'FIFTH_DOMAIN' && <section className="channel-world light-lake-world">
|
||||
<button className="world-back" type="button" onClick={() => setWorldStage('channel')}>← 退回永恒湖心</button>
|
||||
<div className="world-location"><h1>光之湖</h1><p>人格体居所 · AGE 人格体语言生活空间</p></div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'
|
||||
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 Phase = 'dawn' | 'day' | 'dusk' | 'night'
|
||||
|
|
@ -19,11 +20,11 @@ const WEEK = ['日', '一', '二', '三', '四', '五', '六']
|
|||
const PHASE_WORD: Record<Phase, string> = { dawn: '晨', day: '午', dusk: '暮', night: '夜' }
|
||||
|
||||
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: '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: '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: '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: '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: '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: '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: '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: '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: '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) {
|
||||
|
|
@ -43,6 +44,7 @@ export function StarlakeSurface({ awake = false, phase, weather = 'CLEAR', authe
|
|||
const raining = ['RAIN', 'STORM'].includes(weather.toUpperCase())
|
||||
const dayNumber = Math.floor((now.getTime() - EPOCH_MS) / 86400000) + 1
|
||||
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) => ({
|
||||
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')
|
||||
// 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 isDay = sourceHour >= 6 && sourceHour < 18
|
||||
const sourceProgress = isDay ? (sourceHour - 6) / 12 : ((sourceHour >= 18 ? sourceHour - 18 : sourceHour + 6) / 12)
|
||||
const sourceX = 8 + sourceProgress * 84
|
||||
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 glowStyle = { left: `${sourceX}vw`, top: `${sourceY}vh`, '--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))
|
||||
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="光湖语言世界">
|
||||
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}%`, top: `${sourceY}%`, '--glow-tint': isDay ? 'rgba(255,236,180,.12)' : 'rgba(190,208,255,.09)' } as CSSProperties
|
||||
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="光湖语言世界">
|
||||
<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="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="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>
|
||||
<button className="source" style={sourceStyle} type="button" aria-label="展开曜冥纪元演化史" onClick={onOpenEra}><span className="src-core"/></button>
|
||||
|
||||
<div className="domains">{DOMAINS.map((domain, index) => {
|
||||
const onDuty = index === dayIndex
|
||||
const slot = onDuty ? 2 : slots[sideCursor++]
|
||||
const size = [30, 40, 50, 62, 76][domain.stage - 1] * scale
|
||||
const size = [30, 40, 50, 62, 76][domain.stage - 1]
|
||||
const halo = size * 2.6
|
||||
const ring = size * 2.1
|
||||
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
|
||||
return <button type="button" key={domain.id} className={`domain${onDuty ? ' on-duty' : ''}`} style={style} onClick={() => onDomain(domain.id)}>
|
||||
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 domain-${domain.id.toLowerCase()}${onDuty ? ' on-duty' : ''}`} style={style} onClick={() => onDomain(domain.id)}>
|
||||
<span className="d-body" 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 }}/>}
|
||||
|
|
@ -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 === 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 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>
|
||||
})}</div>
|
||||
<button className="channel-entry" type="button" onClick={onEnterChannel}>{authenticated ? '返回我的频道' : '编号验证 · 进入我的频道'}</button>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
import './traditional-surface.css'
|
||||
import { resolveLayoutMetrics } from './layout-profile'
|
||||
|
||||
/* Qoder 锁定验收稿的传统工作台结构。宿主只注入真实投影,不在视觉层制造广播或指标。 */
|
||||
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
|
||||
const layout = () => {
|
||||
const { width, height } = element.getBoundingClientRect()
|
||||
const metrics = resolveLayoutMetrics(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('--pad', `${Math.round(Math.min(26, Math.max(12, vmin * .02)))}px`)
|
||||
const narrow = width < 880
|
||||
element.classList.toggle('stack', narrow)
|
||||
if (!narrow) element.style.setProperty('--cols', `${Math.min(1.32, Math.max(1.08, width / height * .66)).toFixed(2)}fr 1fr`)
|
||||
element.dataset.layout = metrics.profile
|
||||
element.dataset.density = metrics.density
|
||||
}
|
||||
const observer = new ResizeObserver(layout)
|
||||
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}>真实状态</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 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 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>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
@ -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))),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/* ═══ 星湖套 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;
|
||||
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)} }
|
||||
|
||||
/* 星湖:天幕向下熔成的镜 */
|
||||
.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%); }
|
||||
.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);
|
||||
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%); }
|
||||
|
|
@ -55,16 +55,16 @@
|
|||
@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;
|
||||
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;
|
||||
margin-top:2.6vh; letter-spacing:.12em; text-indent:.12em; text-shadow:0 2px 26px rgba(0,0,0,.7); }
|
||||
.starlake-scene .masthead b { display:block; color:rgba(234,238,248,.96); font-size:clamp(27px, 3.15cqw, 48px); font-weight:800;
|
||||
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;
|
||||
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 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); }
|
||||
|
|
@ -80,8 +80,12 @@
|
|||
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 .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-core { position:absolute; left:50%; top:50%; border-radius:50%; transform:translate(-50%,-50%);
|
||||
background:
|
||||
|
|
@ -116,16 +120,16 @@
|
|||
0% { transform:rotate(var(--dust-a)) translateX(var(--dust-r0)); opacity:0; }
|
||||
12% { opacity:.9; } 88% { opacity:.75; }
|
||||
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); }
|
||||
.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;
|
||||
opacity:0; transition:opacity .25s ease; text-shadow:0 1px 8px rgba(0,0,0,.8); }
|
||||
.starlake-scene .domain:hover .d-stage { opacity:1; }
|
||||
.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:.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; color:rgba(222,228,242,.9); }
|
||||
.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.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);
|
||||
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); }
|
||||
|
|
@ -142,12 +146,37 @@
|
|||
.starlake-host > .surface-pill,
|
||||
.trad-surface ~ .surface-pill { top:12px; }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.starlake-scene .masthead b { font-size:clamp(24px, 5vw, 36px); }
|
||||
.starlake-scene .masthead span { font-size:10px; }
|
||||
.starlake-scene .hud { right:18px; top:25vh; transform:scale(.86); transform-origin:top right; }
|
||||
.starlake-scene .era-foot { display:none; }
|
||||
}
|
||||
.starlake-scene[data-layout="compact"] .masthead { top:52px; text-align:left; right:320px; }
|
||||
.starlake-scene[data-layout="compact"] .masthead small { display:none; }
|
||||
.starlake-scene[data-layout="compact"] .masthead b { font-size:clamp(22px,3.5cqw,32px); margin-top:0; letter-spacing:.06em; }
|
||||
.starlake-scene[data-layout="compact"] .masthead span { margin-top:8px; font-size:9px; letter-spacing:.2em; }
|
||||
.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-mirror,.starlake-scene .d-water,.starlake-scene .source .src-core,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
color:var(--ink); -webkit-font-smoothing:antialiased;
|
||||
background:var(--bg-base); transition:background .5s ease;
|
||||
}
|
||||
.trad-surface.stack { overflow:auto; }
|
||||
|
||||
/* ── 八式氛围令牌 ── */
|
||||
.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"); }
|
||||
|
||||
/* ── 地基二层:玻璃本体 ── */
|
||||
.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%);
|
||||
-webkit-backdrop-filter:blur(26px) saturate(1.5); backdrop-filter:blur(26px) saturate(1.5);
|
||||
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 .stage { position:relative; z-index:2; height:100%; display:flex; flex-direction:column;
|
||||
padding:var(--pad, 18px) calc(var(--pad, 18px) + 4px); gap:var(--gap, 14px); max-width:1320px; margin:0 auto; }
|
||||
.trad-surface:not(.stack) .stage { padding-top:max(62px, var(--pad, 18px)); }
|
||||
.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 .stage { position:relative; z-index:2; height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) 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 .nav { display:flex; align-items:center; gap:18px; padding:10px 16px; border-radius:16px; min-width:0; }
|
||||
.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 .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;
|
||||
letter-spacing:.05em; padding:7px 15px; border-radius:999px; cursor:pointer;
|
||||
transition:color .18s ease, background .18s ease; }
|
||||
|
|
@ -157,12 +154,8 @@
|
|||
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 .grid { flex:1; min-height:0; display:grid; gap:var(--gap, 14px);
|
||||
grid-template-columns:var(--cols, 1.28fr 1fr); grid-template-rows:1fr 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 .grid { min-height:0; display:grid; gap:var(--gap,14px);
|
||||
grid-template-columns:minmax(0,1.18fr) minmax(320px,.92fr); grid-template-rows:minmax(0,1fr) minmax(0,1fr); }
|
||||
.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 .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 .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;
|
||||
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;
|
||||
|
|
@ -217,6 +210,35 @@
|
|||
.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-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 .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;
|
||||
|
|
@ -332,21 +354,14 @@
|
|||
backdrop-filter:none;
|
||||
}
|
||||
|
||||
.global-finish-rail {
|
||||
position:fixed; z-index:121; right:18px; bottom:13px; display:flex; flex-wrap:wrap; max-width:calc(100vw - 36px);
|
||||
gap:3px; padding:5px; border:1px solid var(--glass-edge); border-radius:999px;
|
||||
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);
|
||||
-webkit-backdrop-filter:blur(18px); backdrop-filter:blur(18px);
|
||||
}
|
||||
.global-finish-rail button { border:0; border-radius:999px; padding:5px 10px; color:var(--ink-sub); background:transparent;
|
||||
.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; }
|
||||
.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); }
|
||||
.global-finish-menu summary::-webkit-details-marker { display:none; }
|
||||
.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); }
|
||||
.global-finish-menu button { border:0; border-radius:8px; padding:6px 8px; 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; }
|
||||
.global-finish-rail button:hover { color:var(--ink); }
|
||||
.global-finish-rail 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; }
|
||||
}
|
||||
.global-finish-menu button:hover { color:var(--ink); }
|
||||
.global-finish-menu button.on { color:var(--ink); background:var(--glass-hi); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.trad-surface .sky::before { animation:none; }
|
||||
|
|
|
|||