feat: admit complete Qoder surfaces into numbered HoloLake
This commit is contained in:
parent
fdc1bd3c0a
commit
fb8607f248
14 changed files with 1016 additions and 31 deletions
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"schema": "hololake.production-install-acceptance/v1",
|
||||||
|
"record_id": "HLP-PRODUCTION-INSTALL-20260819-001",
|
||||||
|
"state": "PASS",
|
||||||
|
"version": "0.5.0",
|
||||||
|
"installed_path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake.app",
|
||||||
|
"visible_hololake_app_count": 1,
|
||||||
|
"previous_app_recovery_path": "/Users/bingshuolingdianyuanhe/.Trash/HoloLake-pre-qoder-numbered-ui-20260819-0659.app",
|
||||||
|
"binary_sha256": "b0422bb18df521ace54ff1a52cd47121d92833dd78f165845deba7806686c7ac",
|
||||||
|
"cdhash": "42640f7ce56e96403609899095c77ea6171df07d",
|
||||||
|
"team_identifier": "825A9L3G7Q",
|
||||||
|
"developer_id_signature": "PASS",
|
||||||
|
"launch": "PASS",
|
||||||
|
"real_data_rehydration": "PASS",
|
||||||
|
"notarization": "NOT_CLAIMED_THIS_LOCAL_BUILD",
|
||||||
|
"updater_artifact_signing": "NOT_COMPLETED_MISSING_PRIVATE_UPDATER_KEY_IN_CURRENT_PROCESS"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"schema": "hololake.qoder-surface-live-acceptance/v1",
|
||||||
|
"record_id": "HLP-QODER-SURFACE-ACCEPTANCE-20260819-001",
|
||||||
|
"module_number": "HLP-MOD-OFFICIAL-DYNAMIC-WORLD-SURFACE-0001",
|
||||||
|
"state": "PASS",
|
||||||
|
"tested_app": "src-tauri/target/debug/bundle/macos/HoloLake.app",
|
||||||
|
"developer_id_signature": "PASS",
|
||||||
|
"team_identifier": "825A9L3G7Q",
|
||||||
|
"binary_sha256": "fb1d0497f295e1228c9b2ac86e33082332f226aae6e32b9050cf6c5de30bd512",
|
||||||
|
"cdhash": "90e365b23961a6b842590869df655ec35a55b66e",
|
||||||
|
"checks": {
|
||||||
|
"traditional_locked_layout": "PASS",
|
||||||
|
"traditional_real_data_hydration": "PASS",
|
||||||
|
"traditional_light_finish_readability": "PASS_SNOW",
|
||||||
|
"language_world_five_domains_no_overlap": "PASS_1229x768",
|
||||||
|
"language_world_real_weather": "PASS_CLOUD",
|
||||||
|
"theme_persists_into_channel": "PASS",
|
||||||
|
"theme_persists_into_web_novel": "PASS",
|
||||||
|
"theme_persists_into_education": "PASS",
|
||||||
|
"new_chapter_modal_opens": "PASS",
|
||||||
|
"created_chapter_survives_restart": "PASS_1_CHAPTER",
|
||||||
|
"work_summary_refresh_after_create": "PASS_1_CHAPTER_ON_WORK_CARD",
|
||||||
|
"idle_clock_stops": "PASS",
|
||||||
|
"idle_screenshot_psnr_db": 59.502493
|
||||||
|
},
|
||||||
|
"reference_comparison": "audit/qoder-traditional-reference-vs-hololake-0.5.0.jpg",
|
||||||
|
"public_notarization": "NOT_CLAIMED_BY_DEBUG_ACCEPTANCE"
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
|
|
@ -25,8 +25,13 @@
|
||||||
"unavailable_behavior": "BEIJING_TIME_ONLY_NO_FAKE_WEATHER"
|
"unavailable_behavior": "BEIJING_TIME_ONLY_NO_FAKE_WEATHER"
|
||||||
},
|
},
|
||||||
"visual_lock": {
|
"visual_lock": {
|
||||||
"layout": "EXISTING_OFFICIAL_FIVE_LAKE_SHELL_UNCHANGED",
|
"layout": "QODER_LOCKED_STARLAKE_AND_TRADITIONAL_SURFACES_RESPONSIVELY_ADAPTED",
|
||||||
"themes": ["夜湖星光", "晨湖曦光", "星云紫夜", "烛畔暖湖", "清浅澄湖"],
|
"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",
|
||||||
"climate_changes_routing_permission_or_fact": false,
|
"climate_changes_routing_permission_or_fact": false,
|
||||||
"daily_sampling_domain_changes_open_domain": false,
|
"daily_sampling_domain_changes_open_domain": false,
|
||||||
"real_city_exposed_in_ui": false,
|
"real_city_exposed_in_ui": false,
|
||||||
|
|
@ -36,18 +41,26 @@
|
||||||
},
|
},
|
||||||
"donor_disposition": {
|
"donor_disposition": {
|
||||||
"world_climate": "EXTRACTED_REWRITTEN_AND_NUMBERED",
|
"world_climate": "EXTRACTED_REWRITTEN_AND_NUMBERED",
|
||||||
"starlake_surface": "BEHAVIOR_MERGED_INTO_EXISTING_OFFICIAL_SHELL_NOT_COPIED",
|
"starlake_surface": "QODER_LOCKED_SOURCE_ADMITTED_WITH_RESPONSIVE_AND_IDLE_MOTION_ADAPTER",
|
||||||
"traditional_surface": "REJECTED_DUPLICATE_SHELL_EIGHT_THEMES_AND_SIMULATED_STATUS",
|
"traditional_surface": "QODER_LOCKED_SOURCE_ADMITTED_WITH_REAL_NUMBERED_PROJECTIONS_ONLY",
|
||||||
"traditional_surface_css": "REJECTED_CONFLICTS_WITH_LOCKED_FIVE_LAKE_TOKENS",
|
"traditional_surface_css": "QODER_LOCKED_EIGHT_FINISH_TOKEN_SYSTEM_ADMITTED_AND_SCOPED_ACROSS_ALL_PAGES",
|
||||||
"fake_broadcasts_and_fake_metrics": "REJECTED"
|
"fake_broadcasts_and_fake_metrics": "REJECTED"
|
||||||
},
|
},
|
||||||
|
"qoder_source_provenance": {
|
||||||
|
"root": "/Volumes/JZAO/铸渊-ICE-GL-ZY001/OUT-输出/UI-预览-20260818/deploy-staging",
|
||||||
|
"main_tsx_sha256": "7dab8596ec0bd2609b1b23b175f1f14f15f2aa9af847eb605692c993d2e876e8",
|
||||||
|
"traditional_surface_tsx_sha256": "c713dc27681e07c510a2125816dcefb3255252fc0b570e350af7a98f57fc220d",
|
||||||
|
"traditional_surface_css_sha256": "3e6c446fb84a16ed23b0fd51466ad9723d0c0670ee6ddf6653f61237bd00baa6",
|
||||||
|
"starlake_surface_css_sha256": "471e3edef02236e6463144ac75b526f0f98782b5476b9db786e7cd0614a83227",
|
||||||
|
"acceptance_html_sha256": "7a41e426f1576fe0e25182e0c2efee65302b064d86363c890b0f235386a9c070"
|
||||||
|
},
|
||||||
"current_acceptance": {
|
"current_acceptance": {
|
||||||
"state": "PASS_SIGNED_NUMBERED_REAL_UI_AND_RESTART",
|
"state": "PASS_NUMBERED_QODER_SURFACES_REAL_DATA_RESPONSIVE_IDLE_AND_RESTART",
|
||||||
"module_package_sha256": "b6ba13b9f70bd617b2a303ec2eb0ea11d5354d10118c7f3e5179ae8100cd2602",
|
"module_package_sha256": "b6ba13b9f70bd617b2a303ec2eb0ea11d5354d10118c7f3e5179ae8100cd2602",
|
||||||
"module_package_signature": "PASS_EMBEDDED_PRODUCT_TRUST",
|
"module_package_signature": "PASS_EMBEDDED_PRODUCT_TRUST",
|
||||||
"numbered_route": "HLP-NIPC-MOD-0031/HLP-NIPC-OP-0147/HLP-NIPC-TGT-0031",
|
"numbered_route": "HLP-NIPC-MOD-0031/HLP-NIPC-OP-0147/HLP-NIPC-TGT-0031",
|
||||||
"signed_app_binary_sha256": "5612519f56ea83cfd167cd099cc2cb27de9da057b262716bb71ff38714c76c8f",
|
"signed_app_binary_sha256": "b0422bb18df521ace54ff1a52cd47121d92833dd78f165845deba7806686c7ac",
|
||||||
"signed_app_cdhash": "5cc0836a1f12f42f9f48196ce691d6f4cde2f801",
|
"signed_app_cdhash": "42640f7ce56e96403609899095c77ea6171df07d",
|
||||||
"apple_team_identifier": "825A9L3G7Q",
|
"apple_team_identifier": "825A9L3G7Q",
|
||||||
"module_receipts": {
|
"module_receipts": {
|
||||||
"install": "2bb903cd369c8f57432c59367fd5494ee89a75214d5129511190e2337cecdbc4",
|
"install": "2bb903cd369c8f57432c59367fd5494ee89a75214d5129511190e2337cecdbc4",
|
||||||
|
|
@ -55,8 +68,14 @@
|
||||||
"self_test_pass": "404a0eeddb866bc3050b407c586c036f3707db577e1edf9f1cf3129c1cc9cc1d"
|
"self_test_pass": "404a0eeddb866bc3050b407c586c036f3707db577e1edf9f1cf3129c1cc9cc1d"
|
||||||
},
|
},
|
||||||
"real_weather_readback": "PASS_VERIFIED_LIVE_NIGHT_CLOUD_WITHOUT_CITY_OR_COORDINATES",
|
"real_weather_readback": "PASS_VERIFIED_LIVE_NIGHT_CLOUD_WITHOUT_CITY_OR_COORDINATES",
|
||||||
"restart_restore": "PASS_ACTIVE_MODULE_AND_REAL_CLIMATE_RESTORED",
|
"restart_restore": "PASS_ACTIVE_MODULE_REAL_CLIMATE_THEME_AND_WEB_NOVEL_CHAPTER_RESTORED",
|
||||||
"official_shell_preserved": "PASS_FIVE_LAKE_LAYOUT_AND_FIVE_THEMES_UNCHANGED",
|
"official_shell_preserved": "QODER_LOCKED_SURFACES_ADMITTED_WITH_NUMBERED_FUNCTION_ROUTING_UNCHANGED",
|
||||||
|
"visual_comparison": "audit/qoder-traditional-reference-vs-hololake-0.5.0.jpg",
|
||||||
|
"idle_static_psnr_db": 59.502493,
|
||||||
|
"idle_static_clock_unchanged": true,
|
||||||
|
"web_novel_new_chapter_modal": "PASS",
|
||||||
|
"web_novel_created_chapter_restart_persistence": "PASS_1_CHAPTER_VISIBLE_AFTER_RESTART",
|
||||||
|
"traditional_theme_downstream_pages": ["CHANNEL", "WEB_NOVEL", "EDUCATION"],
|
||||||
"notarization": "FINAL_RELEASE_CANDIDATE_PENDING"
|
"notarization": "FINAL_RELEASE_CANDIDATE_PENDING"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,16 +169,17 @@
|
||||||
"state": "ADMITTED_INSTALLED_RUNTIME_ACCEPTED",
|
"state": "ADMITTED_INSTALLED_RUNTIME_ACCEPTED",
|
||||||
"paths": [
|
"paths": [
|
||||||
"contracts/dynamic-language-world-visual-system.json",
|
"contracts/dynamic-language-world-visual-system.json",
|
||||||
"src/StarlakeSurface.tsx",
|
"src/modules/qoder-surface/StarlakeSurface.tsx",
|
||||||
"src/starlake-surface.css",
|
"src/modules/qoder-surface/starlake-surface.css",
|
||||||
"src/traditional-surface.tsx",
|
"src/modules/qoder-surface/TraditionalSurface.tsx",
|
||||||
"src/traditional-surface.css",
|
"src/modules/qoder-surface/traditional-surface.css",
|
||||||
|
"src/modules/qoder-surface/visual-balance.ts",
|
||||||
"src-tauri/src/world_climate.rs"
|
"src-tauri/src/world_climate.rs"
|
||||||
],
|
],
|
||||||
"runtime_module_number": "HLP-MOD-OFFICIAL-DYNAMIC-WORLD-SURFACE-0001",
|
"runtime_module_number": "HLP-MOD-OFFICIAL-DYNAMIC-WORLD-SURFACE-0001",
|
||||||
"numbered_ipc_module": "HLP-NIPC-MOD-0031",
|
"numbered_ipc_module": "HLP-NIPC-MOD-0031",
|
||||||
"acceptance_evidence": "contracts/dynamic-language-world-visual-system.json#current_acceptance",
|
"acceptance_evidence": "contracts/dynamic-language-world-visual-system.json#current_acceptance",
|
||||||
"boundary": "REALITY_TIME_AND_PUBLIC_WEATHER_VISUAL_PROJECTION_ONLY; OFFICIAL_FIVE_LAKE_SHELL_AND_ROUTING_UNCHANGED; NO_FAKE_WEATHER_OR_SIMULATED_STATUS"
|
"boundary": "QODER_LOCKED_VISUAL_SOURCE_PLUS_REALITY_TIME_AND_PUBLIC_WEATHER_PROJECTION; ALL_FUNCTION_ROUTES_REMAIN_NUMBERED; IDLE_STATIC; NO_FAKE_WEATHER_BROADCAST_OR_METRICS"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"admission_gate": [
|
"admission_gate": [
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@ const modulePackage = JSON.parse(read('fixtures/module-packages/HLP-MOD-OFFICIAL
|
||||||
const rust = read('src-tauri/src/world_climate.rs')
|
const rust = read('src-tauri/src/world_climate.rs')
|
||||||
const frontend = read('src/main.tsx')
|
const frontend = read('src/main.tsx')
|
||||||
const styles = read('src/styles.css')
|
const styles = read('src/styles.css')
|
||||||
|
const starlake = read('src/modules/qoder-surface/StarlakeSurface.tsx')
|
||||||
|
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')
|
||||||
|
|
||||||
test('dynamic world surface is signed, opt-in and cannot mutate world facts', () => {
|
test('dynamic world surface is signed, opt-in and cannot mutate world facts', () => {
|
||||||
assert.equal(modulePackage.manifest.moduleNumber, 'HLP-MOD-OFFICIAL-DYNAMIC-WORLD-SURFACE-0001')
|
assert.equal(modulePackage.manifest.moduleNumber, 'HLP-MOD-OFFICIAL-DYNAMIC-WORLD-SURFACE-0001')
|
||||||
|
|
@ -31,13 +36,30 @@ test('climate query crosses the unique numbered route and fails closed before ac
|
||||||
assert.doesNotMatch(frontend, /from ['"]@tauri-apps\/api\/core['"]/)
|
assert.doesNotMatch(frontend, /from ['"]@tauri-apps\/api\/core['"]/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('official shell and five lake themes remain the only admitted layout', () => {
|
test('Qoder locked two-track surface is admitted without simulated product state', () => {
|
||||||
assert.equal(contract.visual_lock.layout, 'EXISTING_OFFICIAL_FIVE_LAKE_SHELL_UNCHANGED')
|
assert.equal(contract.visual_lock.layout, 'QODER_LOCKED_STARLAKE_AND_TRADITIONAL_SURFACES_RESPONSIVELY_ADAPTED')
|
||||||
assert.equal(contract.visual_lock.themes.length, 5)
|
assert.equal(contract.visual_lock.language_world_themes.length, 5)
|
||||||
assert.equal(contract.donor_disposition.traditional_surface, 'REJECTED_DUPLICATE_SHELL_EIGHT_THEMES_AND_SIMULATED_STATUS')
|
assert.equal(contract.visual_lock.traditional_finishes.length, 8)
|
||||||
|
assert.equal(contract.visual_lock.all_downstream_pages_inherit_active_surface_tokens, true)
|
||||||
assert.match(frontend, /夜湖星光/)
|
assert.match(frontend, /夜湖星光/)
|
||||||
assert.match(frontend, /天气源暂不可用;湖面只使用北京时间,不生成假天气。/)
|
assert.match(frontend, /天气源暂不可用;湖面只使用北京时间,不生成假天气。/)
|
||||||
assert.match(styles, /\.world-climate-veil/)
|
assert.match(styles, /\.world-climate-veil/)
|
||||||
assert.doesNotMatch(frontend, /语料库新增 217 条/)
|
assert.match(starlake, /ResizeObserver/)
|
||||||
assert.doesNotMatch(frontend, /频道活动 · 七日/)
|
assert.match(starlakeStyles, /\.starlake-scene\.awake/)
|
||||||
|
assert.match(traditional, /broadcasts\.map/)
|
||||||
|
assert.match(traditionalStyles, /\.official-world\.surface-traditional/)
|
||||||
|
assert.match(visualBalance, /contrastRatio/)
|
||||||
|
assert.doesNotMatch(traditional, /语料库新增 217 条/)
|
||||||
|
assert.doesNotMatch(traditional, /const BROADCAST_POOL/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ambient motion sleeps and the Qoder source hashes remain pinned', () => {
|
||||||
|
assert.equal(contract.visual_lock.ambient_motion_requires_recent_human_pointer_or_keyboard_activity, true)
|
||||||
|
assert.equal(contract.visual_lock.idle_visual_state, 'STATIC')
|
||||||
|
assert.match(frontend, /setTimeout\(\(\) => \{[\s\S]*setMotionAwake\(false\)/)
|
||||||
|
assert.match(starlake, /if \(!awake\) return/)
|
||||||
|
assert.match(traditional, /if \(!awake\) return/)
|
||||||
|
assert.match(starlakeStyles, /animation-play-state:paused/)
|
||||||
|
assert.match(traditionalStyles, /animation-play-state:paused/)
|
||||||
|
assert.equal(contract.qoder_source_provenance.main_tsx_sha256, '7dab8596ec0bd2609b1b23b175f1f14f15f2aa9af847eb605692c993d2e876e8')
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import { numberedInvoke as invoke } from './modules/numbered-ipc'
|
||||||
import { splitFrontmatter, documentOutline, jumpToHeading, MarkdownDocument, markdownHtml } from './modules/knowledge-render'
|
import { splitFrontmatter, documentOutline, jumpToHeading, MarkdownDocument, markdownHtml } from './modules/knowledge-render'
|
||||||
import { NativeCompositionStudio, type CompositionDimension, type CompositionMeasure, type NativeCompositionProjection, type ProjectionView } from './modules/native-composition'
|
import { NativeCompositionStudio, type CompositionDimension, type CompositionMeasure, type NativeCompositionProjection, type ProjectionView } from './modules/native-composition'
|
||||||
import type { ChannelWorkbenchSnapshot } from './modules/channel-workbench'
|
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'
|
||||||
|
|
||||||
const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').then((module) => ({ default: module.ChannelWorkbenchStudio })))
|
const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').then((module) => ({ default: module.ChannelWorkbenchStudio })))
|
||||||
const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
|
const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
|
||||||
|
|
@ -590,6 +593,8 @@ function HoloLakeApp() {
|
||||||
}, 2400)
|
}, 2400)
|
||||||
}, [])
|
}, [])
|
||||||
const [theme, setTheme] = useState<ThemeId>(() => (window.localStorage.getItem('hololake-theme') as ThemeId) || 'night')
|
const [theme, setTheme] = useState<ThemeId>(() => (window.localStorage.getItem('hololake-theme') as ThemeId) || 'night')
|
||||||
|
const [surface, setSurface] = useState<'world' | 'traditional'>(() => window.localStorage.getItem('hololake-surface') === 'traditional' ? 'traditional' : 'world')
|
||||||
|
const [traditionalFinish, setTraditionalFinish] = useState<FinishId>(() => (window.localStorage.getItem('hololake-finish') as FinishId) || 'aurora')
|
||||||
const [view, setView] = useState<ViewId>('overview')
|
const [view, setView] = useState<ViewId>('overview')
|
||||||
const [worldStage, setWorldStage] = useState<WorldStage>('domain')
|
const [worldStage, setWorldStage] = useState<WorldStage>('domain')
|
||||||
const [toolReturnStage, setToolReturnStage] = useState<WorldStage>('channel')
|
const [toolReturnStage, setToolReturnStage] = useState<WorldStage>('channel')
|
||||||
|
|
@ -1851,11 +1856,91 @@ function HoloLakeApp() {
|
||||||
const enterpriseRelationshipPending = Boolean(repoLogin && repoLogin.domain !== 'FIFTH_DOMAIN' && enterpriseEntry && enterpriseEntry.relationship_confirmation?.decision !== 'CONFIRM')
|
const enterpriseRelationshipPending = Boolean(repoLogin && repoLogin.domain !== 'FIFTH_DOMAIN' && enterpriseEntry && enterpriseEntry.relationship_confirmation?.decision !== 'CONFIRM')
|
||||||
const enterpriseResponsibilityPending = Boolean(repoLogin && repoLogin.domain !== 'FIFTH_DOMAIN' && enterpriseEntry && !enterpriseRelationshipPending && enterpriseEntry.responsibility_receipt?.decision !== 'ACCEPT')
|
const enterpriseResponsibilityPending = Boolean(repoLogin && repoLogin.domain !== 'FIFTH_DOMAIN' && enterpriseEntry && !enterpriseRelationshipPending && enterpriseEntry.responsibility_receipt?.decision !== 'ACCEPT')
|
||||||
const climateClasses = worldClimate ? ` climate-${worldClimate.weatherKind.toLowerCase()} phase-${worldClimate.timePhase.toLowerCase()}` : ''
|
const climateClasses = worldClimate ? ` climate-${worldClimate.weatherKind.toLowerCase()} phase-${worldClimate.timePhase.toLowerCase()}` : ''
|
||||||
|
const chooseSurface = (next: 'world' | 'traditional') => {
|
||||||
|
setSurface(next)
|
||||||
|
window.localStorage.setItem('hololake-surface', next)
|
||||||
|
}
|
||||||
|
const setFinish = (next: FinishId) => {
|
||||||
|
setTraditionalFinish(next)
|
||||||
|
window.localStorage.setItem('hololake-finish', next)
|
||||||
|
}
|
||||||
|
const surfacePill = <div className="surface-pill" role="group" aria-label="界面切换">
|
||||||
|
<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 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(() => {
|
||||||
|
const result = Array<number>(7).fill(0)
|
||||||
|
const today = new Date(); today.setHours(0, 0, 0, 0)
|
||||||
|
for (const event of personal.recentEvents) {
|
||||||
|
const occurred = new Date(event.occurredAtUnixMs); occurred.setHours(0, 0, 0, 0)
|
||||||
|
const daysAgo = Math.floor((today.getTime() - occurred.getTime()) / 86400000)
|
||||||
|
if (daysAgo >= 0 && daysAgo < 7) result[6 - daysAgo] += 1
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}, [personal.recentEvents])
|
||||||
|
const domainLabel = domainDisplayName(worldClimate?.activeDomain || repoLogin?.domain || 'MAIN_DOMAIN')
|
||||||
|
const visualBalance = resolveVisualBalance(traditionalFinish, worldClimate?.timePhase, worldClimate?.weatherKind)
|
||||||
|
const traditionalBroadcasts: TraditionalBroadcast[] = personal.recentEvents.slice(0, 4).map((event) => ({
|
||||||
|
id: event.eventId,
|
||||||
|
domain: repoLogin?.domain === 'FIFTH_DOMAIN' ? '第五域' : '零感域',
|
||||||
|
message: event.summary,
|
||||||
|
time: new Date(event.occurredAtUnixMs).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }),
|
||||||
|
}))
|
||||||
|
if (!traditionalBroadcasts.length) {
|
||||||
|
traditionalBroadcasts.push(
|
||||||
|
{ id: 'number-root', domain: '主域', message: `编号入口 ${zeroPoint?.route === 'verified' ? '已校验' : '等待验证'}`, time: '当前' },
|
||||||
|
{ id: 'module-root', domain: '分域', message: `签名模块 ${personal.modules.length} 项`, time: '当前' },
|
||||||
|
{ id: 'weather-root', domain: '零域', message: worldClimate?.state === 'VERIFIED_LIVE' ? '现实天象投影已校验' : '天气不可用时不生成假数据', time: '当前' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const traditionalChannels: TraditionalChannel[] = [
|
||||||
|
{ color: 'var(--accent)', title: '人格原生代码频道', meta: '当前账号本机 Git 投影', tag: userPncc?.state === 'READY' ? '已认证' : '待接入', tone: 'var(--good)', count: codeChannels.channels.length },
|
||||||
|
{ color: 'var(--accent2)', title: '知识库', meta: '本机内容指纹投影', tag: '原生', tone: 'var(--accent2)', count: knowledge.uniqueDocumentCount },
|
||||||
|
{ color: 'var(--good)', title: '频道模块', meta: '已登记签名模块', tag: '编号', tone: 'var(--good)', count: personal.modules.length },
|
||||||
|
{ color: 'var(--warn)', title: '运行回执', meta: '可核验事件链', tag: '证据', tone: 'var(--warn)', count: receipts.length },
|
||||||
|
]
|
||||||
|
const traditionalSystems: TraditionalSystem[] = [
|
||||||
|
{ title: '编号协议基座', meta: '单一编号入口 · 未知路径关闭', tag: zeroPoint?.route === 'verified' ? '已校验' : '待验证', tone: 'var(--good)' },
|
||||||
|
{ title: '耐久化引擎', meta: '本机 Git · 事件与回执留证', tag: status.releaseRecoveryState && status.releaseRecoveryState !== 'NONE' ? status.releaseRecoveryState : '在线', tone: 'var(--good)' },
|
||||||
|
{ title: '天象感知', meta: worldClimate?.state === 'VERIFIED_LIVE' ? `${domainLabel} · 公开天气已校验` : '只使用北京时间 · 不伪造天气', tag: worldClimate ? '已接入' : '待命', tone: 'var(--accent2)' },
|
||||||
|
{ title: '色衡层', meta: '主题语义令牌 · 明暗自动均衡', tag: '守护中', tone: 'var(--good)' },
|
||||||
|
]
|
||||||
|
const openFromTraditional = (target: 'knowledge' | 'channel' | 'era' | 'settings') => {
|
||||||
|
if (target === 'era') { openEraTimeline(); return }
|
||||||
|
if (!repoLogin) { chooseSurface('world'); setGateOpen(true); return }
|
||||||
|
if (target === 'channel') { setWorldStage(repoLogin.domain === 'FIFTH_DOMAIN' ? 'channel' : 'channel'); return }
|
||||||
|
openWorldTool(target === 'knowledge' ? 'knowledge' : 'system')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surface === 'traditional' && worldStage === 'domain') {
|
||||||
|
return <>
|
||||||
|
<TraditionalSurface awake={motionAwake} finish={traditionalFinish} onFinish={setFinish} onActivity={wakeAmbientMotion} onBack={() => chooseSurface('world')} onOpenKnowledge={() => openFromTraditional('knowledge')} onOpenChannel={() => openFromTraditional('channel')} onOpenEra={() => openFromTraditional('era')} onOpenSettings={() => openFromTraditional('settings')} identityLabel={repoLogin ? `${repoLogin.username} · ${domainDisplayName(repoLogin.domain)}` : '光湖语言系统'} version="0.5.0" dayNumber={dayNumber} duty={domainLabel} weatherLabel={worldClimate ? weatherLabel[worldClimate.weatherKind] : '待命'} broadcasts={traditionalBroadcasts} channels={traditionalChannels} systems={traditionalSystems} activityBars={activityBars} activityCount={activityBars.reduce((sum, value) => sum + value, 0)}/>
|
||||||
|
{surfacePill}
|
||||||
|
{eraOpen && eraTimeline && <EraTimelineOverlay timeline={eraTimeline} coordinate={beijingCoordinate} onClose={() => setEraOpen(false)}/>}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!repoLogin && surface === 'world' && gateStage === 'number' && !gateRising) {
|
||||||
|
const activeGate = domainGates.find((gate) => gate.domain === activeDomainInfo)
|
||||||
|
return <div className={`official-world surface-world starlake-host${motionAwake ? ' motion-awake' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
|
||||||
|
{surfacePill}
|
||||||
|
<StarlakeSurface awake={motionAwake} phase={worldClimate?.timePhase.toLowerCase() as 'dawn' | 'day' | 'dusk' | 'night' | undefined} weather={worldClimate?.weatherKind} authenticated={false} onDomain={(domain: DomainId) => setActiveDomainInfo(domain)} onOpenEra={openEraTimeline} onEnterChannel={() => setGateOpen(true)}/>
|
||||||
|
{activeGate && <><button className="domain-info-scrim" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}/><section className={`domain-info-card info-${activeGate.className.slice(2)}`} role="dialog" aria-modal="true" aria-label={`${activeGate.title}系统信息`}><button className="gate-close" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}>×</button><b>{activeGate.title}</b><small>{activeGate.gate}</small><span className="pool-facts">{activeGate.facts.map(([label, value]) => <span className="pool-fact" key={label}><em>{label}</em><strong>{value}</strong></span>)}</span></section></>}
|
||||||
|
{gateOpen && <section className="number-nucleus open starlake-number-entry" title="输入编号进入所属域"><button className="gate-dismiss-layer" type="button" aria-label="关闭编号验证" onClick={() => { setGateOpen(false); setGateMessage('') }}/><div className="nucleus-panel" role="dialog" aria-label="编号验证"><button className="gate-close" type="button" aria-label="关闭编号验证" onClick={() => { setGateOpen(false); setGateMessage('') }}>×</button><h2>编号验证</h2><div className="gate-pod-row"><input id="gate-number" ref={gateInputRef} aria-label="编号" autoFocus maxLength={48} value={gateNumber} placeholder="如 ICE-GL∞" onChange={(event) => onGateInput(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && gateNumber) { event.preventDefault(); void gateVerifyNumber() } }}/><button type="button" className={`gate-inf${gateInf ? ' on' : ''}`} title="编号包含无限符号时启用" aria-pressed={gateInf} onClick={() => setGateInf((value) => !value)}>∞</button></div><button className="gate-submit" disabled={gateBusy || !gateNumber} onClick={() => void gateVerifyNumber()}>{gateBusy ? '正在验证…' : '验证编号'}</button>{gateMessage && <p className="gate-hint">{gateMessage}</p>}</div></section>}
|
||||||
|
{eraOpen && eraTimeline && <EraTimelineOverlay timeline={eraTimeline} coordinate={beijingCoordinate} onClose={() => setEraOpen(false)}/>}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
if (!repoLogin) {
|
if (!repoLogin) {
|
||||||
const resolvedDomain = zeroPoint?.resolvedDomain || ''
|
const resolvedDomain = zeroPoint?.resolvedDomain || ''
|
||||||
const activeGate = domainGates.find((gate) => gate.domain === activeDomainInfo)
|
const activeGate = domainGates.find((gate) => gate.domain === activeDomainInfo)
|
||||||
return <div className={`official-world${motionAwake ? ' motion-awake' : ''}${loginRising ? ' sinking' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
|
return <div data-finish={traditionalFinish} data-tone={visualBalance.tone} data-phase={visualBalance.phase} data-weather={visualBalance.weather} className={`official-world surface-${surface}${motionAwake ? ' motion-awake' : ''}${loginRising ? ' sinking' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
|
||||||
|
{surfacePill}{finishRail}
|
||||||
<LakeAtmosphere awake={motionAwake}/>
|
<LakeAtmosphere awake={motionAwake}/>
|
||||||
<header className="world-titlebar"><b>HoloLake</b><button type="button" aria-label="显示主题" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}>◌</button></header>
|
<header className="world-titlebar"><b>HoloLake</b><button type="button" aria-label="显示主题" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}>◌</button></header>
|
||||||
<main className={`world-scene${gateRising ? ' resolving' : ''}${gateStage === 'key' ? ' resolved' : ''}`}>
|
<main className={`world-scene${gateRising ? ' resolving' : ''}${gateStage === 'key' ? ' resolved' : ''}`}>
|
||||||
|
|
@ -1917,11 +2002,14 @@ function HoloLakeApp() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isZhizhi = repoLogin.domain === 'FIFTH_DOMAIN' && zeroPoint?.userNumber === 'ICE-GL-ZHI∞'
|
const isZhizhi = repoLogin.domain === 'FIFTH_DOMAIN' && zeroPoint?.userNumber === 'ICE-GL-ZHI∞'
|
||||||
return <div className={`official-world signed-in-world${motionAwake ? ' motion-awake' : ''}${climateClasses}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
|
const signedActiveGate = 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} signed-in-world${motionAwake ? ' motion-awake' : ''}${climateClasses}${worldStage === 'domain' && surface === 'world' ? ' qoder-home' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
|
||||||
|
{surfacePill}{finishRail}
|
||||||
<LakeAtmosphere awake={motionAwake}/>
|
<LakeAtmosphere awake={motionAwake}/>
|
||||||
<header className="world-titlebar"><b>HoloLake</b><div className="world-title-actions"><span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button type="button" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}>◌</button><button type="button" onClick={() => void signOutRepo()}>退出</button></div></header>
|
<header className="world-titlebar"><b>HoloLake</b><div className="world-title-actions"><span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button type="button" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}>◌</button><button type="button" onClick={() => void signOutRepo()}>退出</button></div></header>
|
||||||
<main className="world-scene signed-in-scene">
|
<main className="world-scene signed-in-scene">
|
||||||
{worldStage === 'domain' && <section className="domain-home">
|
{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">
|
||||||
<div className="world-location"><h1>{repoLogin.domain === 'FIFTH_DOMAIN' ? domainDisplayName(repoLogin.domain) : '光湖零感域'}</h1><p>{repoLogin.domain === 'FIFTH_DOMAIN' ? '世界正在发生什么' : '公共工作入口 · 世界正在发生什么'}</p></div>
|
<div className="world-location"><h1>{repoLogin.domain === 'FIFTH_DOMAIN' ? domainDisplayName(repoLogin.domain) : '光湖零感域'}</h1><p>{repoLogin.domain === 'FIFTH_DOMAIN' ? '世界正在发生什么' : '公共工作入口 · 世界正在发生什么'}</p></div>
|
||||||
<div className="broadcast-stream"><p><i/>光湖公共灯塔 · 在线</p><p><i/>协议版本 · {enterpriseEntry?.registry_version || 'HLDP v1.0'}</p>{repoLogin.domain !== 'FIFTH_DOMAIN' && <p><i/>本人责任域 · {domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)}</p>}<p><i/>HoloLake · V0.5.0</p></div>
|
<div className="broadcast-stream"><p><i/>光湖公共灯塔 · 在线</p><p><i/>协议版本 · {enterpriseEntry?.registry_version || 'HLDP v1.0'}</p>{repoLogin.domain !== 'FIFTH_DOMAIN' && <p><i/>本人责任域 · {domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)}</p>}<p><i/>HoloLake · V0.5.0</p></div>
|
||||||
<LakePool className="home-primary" title={repoLogin.domain === 'FIFTH_DOMAIN' ? '永恒湖心系统' : '光湖频道'} meta={repoLogin.domain === 'FIFTH_DOMAIN' ? '进入私人系统' : `${domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)} · 责任工作入口`} open onClick={() => setWorldStage(repoLogin.domain === 'FIFTH_DOMAIN' ? (isZhizhi ? 'heart' : 'channel') : 'channel')}/>
|
<LakePool className="home-primary" title={repoLogin.domain === 'FIFTH_DOMAIN' ? '永恒湖心系统' : '光湖频道'} meta={repoLogin.domain === 'FIFTH_DOMAIN' ? '进入私人系统' : `${domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)} · 责任工作入口`} open onClick={() => setWorldStage(repoLogin.domain === 'FIFTH_DOMAIN' ? (isZhizhi ? 'heart' : 'channel') : 'channel')}/>
|
||||||
|
|
@ -2021,6 +2109,7 @@ function HoloLakeApp() {
|
||||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'education' ? renderEducation() : view === 'webNovel' ? renderWebNovel() : view === 'mobileSync' ? renderMobileSync() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'education' ? renderEducation() : view === 'webNovel' ? renderWebNovel() : view === 'mobileSync' ? renderMobileSync() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||||
</section>}
|
</section>}
|
||||||
</main>
|
</main>
|
||||||
|
{worldStage === 'domain' && surface === 'world' && signedActiveGate && <><button className="domain-info-scrim" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}/><section className={`domain-info-card info-${signedActiveGate.className.slice(2)}`} role="dialog" aria-modal="true" aria-label={`${signedActiveGate.title}系统信息`}><button className="gate-close" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}>×</button><b>{signedActiveGate.title}</b><small>{signedActiveGate.gate}</small><span className="pool-facts">{signedActiveGate.facts.map(([label, value]) => <span className="pool-fact" key={label}><em>{label}</em><strong>{value}</strong></span>)}</span></section></>}
|
||||||
<footer className="world-footer"><b>光湖语言系统 · 通用人工智能操作平台</b><span>GH-AIOS</span></footer>
|
<footer className="world-footer"><b>光湖语言系统 · 通用人工智能操作平台</b><span>GH-AIOS</span></footer>
|
||||||
{eraOpen && eraTimeline && <EraTimelineOverlay timeline={eraTimeline} coordinate={beijingCoordinate} onClose={() => setEraOpen(false)}/>}
|
{eraOpen && eraTimeline && <EraTimelineOverlay timeline={eraTimeline} coordinate={beijingCoordinate} onClose={() => setEraOpen(false)}/>}
|
||||||
{enterpriseEntryBusy && <div className="enterprise-gate"><section><p>正在读取企业责任登记……</p></section></div>}
|
{enterpriseEntryBusy && <div className="enterprise-gate"><section><p>正在读取企业责任登记……</p></section></div>}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
.education-world{min-height:100%;padding:28px;color:#eaf3f3;background:radial-gradient(circle at 75% 0,rgba(84,210,190,.13),transparent 36%),linear-gradient(150deg,#0b1216,#111c21 58%,#0b1318)}
|
.education-world{min-height:100%;padding:28px;color:var(--content-primary);background:radial-gradient(circle at 75% 0,color-mix(in srgb,var(--primitive-cool-glow) 13%,transparent),transparent 36%),linear-gradient(150deg,var(--surface-depth),var(--surface-horizon) 58%,var(--surface-depth))}
|
||||||
.education-world button,.education-world input{font:inherit}.education-hero{display:grid;grid-template-columns:auto 1fr auto;gap:22px;align-items:start}.education-hero>button{background:transparent;border:0;color:#91aaa9;padding:10px}.education-hero span{font-size:11px;letter-spacing:.18em;color:#74d6c5}.education-hero h1{font-size:38px;margin:5px 0}.education-hero p{margin:0;color:#9db1b2}.education-hero aside{display:flex;flex-direction:column;align-items:flex-end;gap:5px}.education-hero aside b{color:#79d9c7}.education-hero aside small{color:#718588;max-width:260px;text-align:right}
|
.education-world button,.education-world input{font:inherit}.education-hero{display:grid;grid-template-columns:auto 1fr auto;gap:22px;align-items:start}.education-hero>button{background:transparent;border:0;color:var(--content-secondary);padding:10px}.education-hero span{font-size:11px;letter-spacing:.18em;color:var(--accent-light)}.education-hero h1{font-size:38px;margin:5px 0}.education-hero p{margin:0;color:var(--content-secondary)}.education-hero aside{display:flex;flex-direction:column;align-items:flex-end;gap:5px}.education-hero aside b{color:var(--state-ready)}.education-hero aside small{color:var(--content-muted);max-width:260px;text-align:right}
|
||||||
.education-tabs{display:flex;gap:8px;margin:24px 0;border-bottom:1px solid #263a3d}.education-tabs button{border:0;background:transparent;color:#8da3a4;padding:12px 15px;border-bottom:2px solid transparent}.education-tabs button.active{color:#eafffb;border-color:#63d5c2}
|
.education-tabs{display:flex;gap:8px;margin:24px 0;border-bottom:1px solid var(--panel-edge)}.education-tabs button{border:0;background:transparent;color:var(--content-secondary);padding:12px 15px;border-bottom:2px solid transparent}.education-tabs button.active{color:var(--content-primary);border-color:var(--accent-light)}
|
||||||
.education-overview{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.education-overview>article,.education-overview>section,.education-import,.education-action-layout>aside,.education-action-layout>main{background:#121f24;border:1px solid #26393d;border-radius:16px;padding:18px}.education-overview>article{display:flex;flex-direction:column;gap:8px}.education-overview>article strong{font-size:30px}.education-overview button,.education-editor-shell button,.education-action-layout button,.education-import button{border:1px solid #345155;background:#172b30;color:#dffbf6;border-radius:9px;padding:9px 12px}.education-overview>section{grid-column:span 2}.education-overview>section>button{width:100%;display:flex;justify-content:space-between;margin-top:8px;text-align:left}.education-overview small{color:#8ba0a2}
|
.education-overview{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.education-overview>article,.education-overview>section,.education-import,.education-action-layout>aside,.education-action-layout>main{background:var(--panel-bg);border:1px solid var(--panel-edge);border-radius:16px;padding:18px}.education-overview>article{display:flex;flex-direction:column;gap:8px}.education-overview>article strong{font-size:30px}.education-overview button,.education-editor-shell button,.education-action-layout button,.education-import button{border:1px solid var(--button-secondary-edge);background:var(--button-secondary-bg);color:var(--button-secondary-text);border-radius:9px;padding:9px 12px}.education-overview>section{grid-column:span 2}.education-overview>section>button{width:100%;display:flex;justify-content:space-between;margin-top:8px;text-align:left}.education-overview small{color:var(--content-muted)}
|
||||||
.education-editor-shell{display:grid;grid-template-columns:230px minmax(0,1fr);gap:14px;min-height:650px}.education-editor-shell>aside{display:flex;flex-direction:column;gap:7px;background:#0e191d;border:1px solid #26393d;border-radius:14px;padding:12px}.education-editor-shell>aside button{text-align:left;display:flex;flex-direction:column}.education-editor-shell button.active{border-color:#62d3c0;background:#1b3537}.education-editor-shell>main{min-width:0;background:#f7faf9;color:#172326;border-radius:14px;overflow:hidden}.education-editor-shell>main>header{display:flex;justify-content:space-between;gap:12px;padding:12px;background:#e8f0ee;border-bottom:1px solid #ccd9d6}.education-editor-shell>main>header input{flex:1;border:0;background:transparent;font-size:19px;font-weight:700;outline:0}.education-editor-shell>main>header>div{display:flex;gap:7px}.education-editor-shell button.danger{color:#ffc4bd;border-color:#734841;background:#33211f}
|
.education-editor-shell{display:grid;grid-template-columns:230px minmax(0,1fr);gap:14px;min-height:650px}.education-editor-shell>aside{display:flex;flex-direction:column;gap:7px;background:var(--panel-bg);border:1px solid var(--panel-edge);border-radius:14px;padding:12px}.education-editor-shell>aside button{text-align:left;display:flex;flex-direction:column}.education-editor-shell button.active{border-color:var(--accent-light);background:color-mix(in srgb,var(--accent-light) 16%,var(--panel-bg))}.education-editor-shell>main{min-width:0;background:#f7faf9;color:#172326;border-radius:14px;overflow:hidden}.education-editor-shell>main>header{display:flex;justify-content:space-between;gap:12px;padding:12px;background:#e8f0ee;border-bottom:1px solid #ccd9d6}.education-editor-shell>main>header input{flex:1;border:0;background:transparent;font-size:19px;font-weight:700;outline:0}.education-editor-shell>main>header>div{display:flex;gap:7px}.education-editor-shell button.danger{color:var(--state-danger);border-color:color-mix(in srgb,var(--state-danger) 45%,var(--panel-edge));background:color-mix(in srgb,var(--state-danger) 12%,var(--panel-bg))}
|
||||||
.education-action-layout{display:grid;grid-template-columns:250px 1fr;gap:14px}.education-action-layout aside{display:flex;flex-direction:column;gap:8px}.education-action-layout main{min-height:420px}.education-metrics{display:flex;gap:12px;margin:18px 0}.education-metrics b{font-size:26px;background:#0d191d;border-radius:12px;padding:14px 22px}.education-metrics small{font-size:11px;display:block;color:#91a6a7}.education-preview{margin-top:15px;padding:16px;border:1px solid #40726d;background:#102725;border-radius:12px}.education-preview p{color:#c9d7d5}.education-preview code{display:block;color:#78dbc9;margin:10px 0}.education-rule{margin-top:18px}
|
.education-action-layout{display:grid;grid-template-columns:250px 1fr;gap:14px}.education-action-layout aside{display:flex;flex-direction:column;gap:8px}.education-action-layout main{min-height:420px}.education-metrics{display:flex;gap:12px;margin:18px 0}.education-metrics b{font-size:26px;background:var(--panel-bg);border-radius:12px;padding:14px 22px}.education-metrics small{font-size:11px;display:block;color:var(--content-muted)}.education-preview{margin-top:15px;padding:16px;border:1px solid var(--panel-edge);background:var(--panel-bg);border-radius:12px}.education-preview p{color:var(--content-secondary)}.education-preview code{display:block;color:var(--accent-light);margin:10px 0}.education-rule{margin-top:18px}
|
||||||
.education-import>header{display:flex;justify-content:space-between;align-items:center}.education-import dl{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.education-import dl div{background:#0d181c;border-radius:10px;padding:12px}.education-import dt{color:#789092;font-size:12px}.education-import dd{margin:5px 0 0;overflow-wrap:anywhere}.education-import section article{display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-top:1px solid #273b3e}.education-import section article div{display:flex;flex-direction:column}.education-import small{color:#89a0a1}.education-message{position:sticky;bottom:14px;background:#17302f;border:1px solid #4e8b83;color:#d8fff8;border-radius:12px;padding:12px 16px;margin:16px 0 0}
|
.education-import>header{display:flex;justify-content:space-between;align-items:center}.education-import dl{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.education-import dl div{background:var(--panel-bg);border-radius:10px;padding:12px}.education-import dt{color:var(--content-muted);font-size:12px}.education-import dd{margin:5px 0 0;overflow-wrap:anywhere}.education-import section article{display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-top:1px solid var(--panel-edge)}.education-import section article div{display:flex;flex-direction:column}.education-import small{color:var(--content-muted)}.education-message{position:sticky;bottom:14px;background:var(--panel-bg);border:1px solid var(--panel-edge);color:var(--content-primary);border-radius:12px;padding:12px 16px;margin:16px 0 0}
|
||||||
@media(max-width:900px){.education-overview{grid-template-columns:repeat(2,1fr)}.education-editor-shell,.education-action-layout{grid-template-columns:1fr}.education-editor-shell>aside{max-height:180px;overflow:auto}.education-hero{grid-template-columns:1fr}.education-hero>button{justify-self:start}.education-import dl{grid-template-columns:1fr}}
|
@media(max-width:900px){.education-overview{grid-template-columns:repeat(2,1fr)}.education-editor-shell,.education-action-layout{grid-template-columns:1fr}.education-editor-shell>aside{max-height:180px;overflow:auto}.education-hero{grid-template-columns:1fr}.education-hero>button{justify-self:start}.education-import dl{grid-template-columns:1fr}}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
import { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'
|
||||||
|
import './starlake-surface.css'
|
||||||
|
|
||||||
|
export type DomainId = 'BRANCH_DOMAIN' | 'ZERO_SENSE_DOMAIN' | 'ZERO_DOMAIN' | 'MAIN_DOMAIN' | 'FIFTH_DOMAIN'
|
||||||
|
export type Phase = 'dawn' | 'day' | 'dusk' | 'night'
|
||||||
|
|
||||||
|
interface StarlakeSurfaceProps {
|
||||||
|
awake?: boolean
|
||||||
|
phase?: Phase
|
||||||
|
weather?: string
|
||||||
|
authenticated?: boolean
|
||||||
|
onDomain: (domain: DomainId) => void
|
||||||
|
onOpenEra: () => void
|
||||||
|
onEnterChannel: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const EPOCH_MS = new Date('2025-04-26T00:00:00+08:00').getTime()
|
||||||
|
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 },
|
||||||
|
]
|
||||||
|
|
||||||
|
function seeded(index: number) {
|
||||||
|
const value = Math.sin(index * 9283.17 + 17.73) * 43758.5453
|
||||||
|
return value - Math.floor(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function phaseOf(hour: number): Phase {
|
||||||
|
return hour < 5 ? 'night' : hour < 9 ? 'dawn' : hour < 16 ? 'day' : hour < 19 ? 'dusk' : 'night'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StarlakeSurface({ awake = false, phase, weather = 'CLEAR', authenticated = false, onDomain, onOpenEra, onEnterChannel }: StarlakeSurfaceProps) {
|
||||||
|
const [now, setNow] = useState(() => new Date())
|
||||||
|
const rootRef = useRef<HTMLElement>(null)
|
||||||
|
const [viewport, setViewport] = useState(() => ({ width: window.innerWidth, height: window.innerHeight }))
|
||||||
|
const livePhase = phase || phaseOf(now.getHours() + now.getMinutes() / 60)
|
||||||
|
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 stars = useMemo(() => Array.from({ length: 170 }, (_, index) => ({
|
||||||
|
id: index,
|
||||||
|
size: .8 + seeded(index + 1) * 1.6,
|
||||||
|
left: seeded(index + 201) * 100,
|
||||||
|
top: seeded(index + 401) * 68,
|
||||||
|
opacity: .2 + seeded(index + 601) * .6,
|
||||||
|
duration: 2.5 + seeded(index + 801) * 5,
|
||||||
|
delay: -seeded(index + 1001) * 7,
|
||||||
|
warm: seeded(index + 1201) < .12,
|
||||||
|
})), [])
|
||||||
|
const lakeStars = useMemo(() => Array.from({ length: 140 }, (_, index) => ({
|
||||||
|
id: index,
|
||||||
|
size: 1 + seeded(index + 1401) * 1.8,
|
||||||
|
left: seeded(index + 1601) * 100,
|
||||||
|
top: Math.pow(seeded(index + 1801), 1.6) * 92,
|
||||||
|
opacity: .22 + seeded(index + 2001) * .5,
|
||||||
|
duration: 2.4 + seeded(index + 2201) * 4,
|
||||||
|
delay: -seeded(index + 2401) * 6,
|
||||||
|
warm: seeded(index + 2601) < .18,
|
||||||
|
})), [])
|
||||||
|
const rain = useMemo(() => Array.from({ length: 90 }, (_, index) => ({
|
||||||
|
id: index,
|
||||||
|
left: seeded(index + 2801) * 108,
|
||||||
|
duration: .9 + seeded(index + 3001) * .8,
|
||||||
|
delay: -seeded(index + 3201) * 2,
|
||||||
|
opacity: .3 + seeded(index + 3401) * .5,
|
||||||
|
})), [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!awake) return
|
||||||
|
setNow(new Date())
|
||||||
|
const timer = window.setInterval(() => setNow(new Date()), 125)
|
||||||
|
return () => window.clearInterval(timer)
|
||||||
|
}, [awake])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = rootRef.current
|
||||||
|
if (!root) return
|
||||||
|
const observer = new ResizeObserver(([entry]) => setViewport({ width: entry.contentRect.width, height: entry.contentRect.height }))
|
||||||
|
observer.observe(root)
|
||||||
|
return () => observer.disconnect()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
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="光湖语言世界">
|
||||||
|
<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>
|
||||||
|
<div className={`tint ${livePhase}`} aria-hidden="true"/>
|
||||||
|
<div className="weather-veil" aria-hidden="true"/>
|
||||||
|
<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>
|
||||||
|
<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 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)}>
|
||||||
|
<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 }}/>}
|
||||||
|
{domain.stage >= 4 && <span className="d-sat-arm"><i className="d-sat"/></span>}
|
||||||
|
{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>
|
||||||
|
</button>
|
||||||
|
})}</div>
|
||||||
|
<button className="channel-entry" type="button" onClick={onEnterChannel}>{authenticated ? '返回我的频道' : '编号验证 · 进入我的频道'}</button>
|
||||||
|
<div className="era-foot">世界在转动 · 湖在记时</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import './traditional-surface.css'
|
||||||
|
|
||||||
|
/* Qoder 锁定验收稿的传统工作台结构。宿主只注入真实投影,不在视觉层制造广播或指标。 */
|
||||||
|
export type FinishId = 'aurora' | 'nebula' | 'abyss' | 'jade' | 'cinnabar' | 'champagne' | 'porcelain' | 'snow'
|
||||||
|
|
||||||
|
export const FINISHES: Array<{ id: FinishId; name: string }> = [
|
||||||
|
{ id: 'aurora', name: '曜夜' }, { id: 'nebula', name: '星辉' }, { id: 'abyss', name: '深海' }, { id: 'jade', name: '翡翠' },
|
||||||
|
{ id: 'cinnabar', name: '朱砂' }, { id: 'champagne', name: '香槟' }, { id: 'porcelain', name: '瓷光' }, { id: 'snow', name: '雪霁' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const BROADCAST_COLOR: Record<string, string> = { 分域: '#7ce8c8', 零感域: '#cfa8ff', 零域: '#ffbe85', 主域: '#a8bcff', 第五域: '#fdeec2' }
|
||||||
|
export interface TraditionalBroadcast { id: string; domain: string; message: string; time: string }
|
||||||
|
export interface TraditionalChannel { color: string; title: string; meta: string; tag: string; tone: string; count: number }
|
||||||
|
export interface TraditionalSystem { title: string; meta: string; tag: string; tone?: string }
|
||||||
|
|
||||||
|
interface TraditionalSurfaceProps {
|
||||||
|
awake?: boolean
|
||||||
|
finish: FinishId
|
||||||
|
onFinish: (finish: FinishId) => void
|
||||||
|
onActivity?: () => void
|
||||||
|
onBack: () => void
|
||||||
|
onOpenKnowledge?: () => void
|
||||||
|
onOpenChannel?: () => void
|
||||||
|
onOpenEra?: () => void
|
||||||
|
onOpenSettings?: () => void
|
||||||
|
identityLabel?: string
|
||||||
|
version: string
|
||||||
|
dayNumber: number
|
||||||
|
duty: string
|
||||||
|
weatherLabel: string
|
||||||
|
broadcasts: TraditionalBroadcast[]
|
||||||
|
channels: TraditionalChannel[]
|
||||||
|
systems: TraditionalSystem[]
|
||||||
|
activityBars: number[]
|
||||||
|
activityCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TraditionalSurface({ awake = false, finish, onFinish, onActivity, onBack, onOpenKnowledge, onOpenChannel, onOpenEra, onOpenSettings, identityLabel = '光湖语言系统', version, dayNumber, duty, weatherLabel, broadcasts, channels, systems, activityBars, activityCount }: TraditionalSurfaceProps) {
|
||||||
|
const rootRef = useRef<HTMLDivElement>(null)
|
||||||
|
const clockRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const tick = () => {
|
||||||
|
const el = clockRef.current
|
||||||
|
if (!el) return
|
||||||
|
const now = new Date()
|
||||||
|
const pad = (value: number, length = 2) => String(value).padStart(length, '0')
|
||||||
|
el.textContent = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}.${pad(now.getMilliseconds(), 3)}`
|
||||||
|
}
|
||||||
|
tick()
|
||||||
|
if (!awake) return
|
||||||
|
const timer = window.setInterval(tick, 125)
|
||||||
|
return () => window.clearInterval(timer)
|
||||||
|
}, [awake])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const element = rootRef.current
|
||||||
|
if (!element) return
|
||||||
|
const layout = () => {
|
||||||
|
const { width, height } = element.getBoundingClientRect()
|
||||||
|
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`)
|
||||||
|
}
|
||||||
|
const observer = new ResizeObserver(layout)
|
||||||
|
observer.observe(element)
|
||||||
|
layout()
|
||||||
|
return () => observer.disconnect()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const pickFinish = (id: FinishId) => { onFinish(id); window.localStorage.setItem('hololake-finish', id) }
|
||||||
|
const maxBar = Math.max(1, ...activityBars)
|
||||||
|
|
||||||
|
return <div className={`trad-surface${awake ? ' awake' : ''}`} data-finish={finish} ref={rootRef} onPointerMove={onActivity} onPointerDown={onActivity} onKeyDown={onActivity}>
|
||||||
|
<div className="sky" aria-hidden="true"/><div className="grain" aria-hidden="true"/>
|
||||||
|
<div className="stage">
|
||||||
|
<nav className="nav glass">
|
||||||
|
<div className="brand">HoloLake<small>GH-AIOS · V{version}</small></div>
|
||||||
|
<div className="tabs"><button type="button" className="on">首页</button><button type="button" onClick={onOpenKnowledge}>知识库</button><button type="button" onClick={onOpenChannel}>频道</button><button type="button" onClick={onOpenEra}>演化史</button><button type="button" onClick={onOpenSettings}>设置</button></div>
|
||||||
|
<div className="me"><i>{identityLabel.startsWith('ICE') ? 'ICE' : 'HL'}</i>{identityLabel}</div>
|
||||||
|
<button type="button" className="back" onClick={onBack}>返回语言世界</button>
|
||||||
|
</nav>
|
||||||
|
<div className="grid">
|
||||||
|
<div className="hero glass">
|
||||||
|
<div className="topline"><span className="lbl">光 湖 历 · 今 日</span><span className="tag">操作唤醒 · 轻量运行</span></div>
|
||||||
|
<div className="day">第 {dayNumber} 天</div>
|
||||||
|
<div className="sub">纪元源点 2025-04-26 · 今日当值 <b>{duty}</b> · 编号路径正常</div>
|
||||||
|
<div className="clock" ref={clockRef}>--:--:--.---</div>
|
||||||
|
<div className="broadcast"><div className="hd"><span className="lbl">世 界 广 播 · 真实投影</span><span className="live"><i/>已校验</span></div><div className="feed">{broadcasts.map(item => <div className="b-item" key={item.id} style={{ '--bc': BROADCAST_COLOR[item.domain] || '#a8bcff' } as React.CSSProperties}><span className="d">{item.domain}</span><span className="m">{item.message}</span><span className="tm">{item.time}</span></div>)}</div></div>
|
||||||
|
<div className="stats"><div className="stat"><span className="lbl">天象 · 自动感知</span><div className="big">{weatherLabel}<em>可验证回传</em></div></div><div className="stat"><span className="lbl">频道活动 · 七日</span><div className="big">{activityCount}<em>次记录</em></div><div className="bars" style={{ height: 34, marginTop: 10 }}>{activityBars.map((value, index) => <i key={index} className={index === activityBars.length - 1 ? 'hot' : ''} style={{ height: `${Math.max(4, Math.round(value / maxBar * 100))}%` }}/>)}</div></div></div>
|
||||||
|
</div>
|
||||||
|
<div className="card glass"><div className="hd"><span className="lbl">频 道 与 文 档</span><span className="tag" style={{ '--tc': 'var(--good)' } as React.CSSProperties}>在册 {channels.length} 项</span></div><div className="rows">{channels.map(channel => <div className="row" key={channel.title}><span className="dot" style={{ color: channel.color, background: channel.color }}/><div className="t"><b>{channel.title}</b><span>{channel.meta}</span></div><span className="tag" style={{ '--tc': channel.tone } as React.CSSProperties}>{channel.tag}</span><span className="num">{channel.count}</span></div>)}</div></div>
|
||||||
|
<div className="card glass"><div className="hd"><span className="lbl">系 统 态</span><span className="tag" style={{ '--tc': 'var(--good)' } as React.CSSProperties}>真实状态</span></div><div className="rows">{systems.map(system => <div className="row" key={system.title}><div className="t"><b>{system.title}</b><span>{system.meta}</span></div><span className="tag" style={system.tone ? { '--tc': system.tone } as React.CSSProperties : undefined}>{system.tag}</span></div>)}</div></div>
|
||||||
|
</div>
|
||||||
|
</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,163 @@
|
||||||
|
/* ═══ 星湖套 v1.3 · 语言世界首页视觉层(作用域 .starlake-scene,不污染功能层)═══ */
|
||||||
|
.starlake-scene { position:fixed; inset:0; z-index:0; pointer-events:none; overflow:hidden;
|
||||||
|
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%); }
|
||||||
|
|
||||||
|
/* 天幕:银河带 + 星屑 */
|
||||||
|
.starlake-scene .milky { position:absolute; inset:-12%; transform:rotate(-14deg);
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, transparent 30%, rgba(148,158,222,.10) 42%, rgba(196,186,238,.16) 47%,
|
||||||
|
rgba(148,158,222,.09) 54%, transparent 66%),
|
||||||
|
linear-gradient(180deg, transparent 40%, rgba(240,230,255,.07) 47%, transparent 55%);
|
||||||
|
filter:blur(1.5px); }
|
||||||
|
.starlake-scene .stars i, .starlake-scene .lakeStars i { position:absolute; border-radius:50%; background:#cfdcff; opacity:var(--go);
|
||||||
|
animation:sl-glint var(--gt) ease-in-out infinite; }
|
||||||
|
@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;
|
||||||
|
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;
|
||||||
|
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%); }
|
||||||
|
.starlake-scene .lake .o1 { width:130vw; height:30vh; top:-8vh; }
|
||||||
|
.starlake-scene .lake .o2 { width:92vw; height:19vh; top:-4vh; border-color:rgba(150,172,255,.09); }
|
||||||
|
.starlake-scene .lake .o3 { width:60vw; height:11vh; top:-1.5vh; border-color:rgba(150,172,255,.07); }
|
||||||
|
|
||||||
|
/* 时间色调层(系统感知自动驱动) */
|
||||||
|
.starlake-scene .tint { position:absolute; inset:0; mix-blend-mode:soft-light; opacity:.5; transition:opacity 1.2s ease, background 1.2s ease; }
|
||||||
|
.starlake-scene .tint.dawn { background:linear-gradient(180deg, rgba(255,176,128,.5), rgba(255,148,120,.3) 55%, transparent 80%); }
|
||||||
|
.starlake-scene .tint.day { background:radial-gradient(120% 70% at 50% 0%, rgba(214,232,255,.46), transparent 70%); }
|
||||||
|
.starlake-scene .tint.dusk { background:linear-gradient(180deg, rgba(255,140,96,.42), rgba(214,96,120,.28) 52%, transparent 80%); }
|
||||||
|
.starlake-scene .tint.night{ background:linear-gradient(180deg, rgba(20,30,70,.44), rgba(8,12,30,.3) 60%, transparent); }
|
||||||
|
.starlake-scene .weather-veil { position:absolute; inset:0; z-index:3; opacity:0; transition:opacity 1.4s ease;
|
||||||
|
background:linear-gradient(180deg, rgba(14,18,30,.5), rgba(10,14,24,.42)); }
|
||||||
|
.starlake-scene .rainfall { position:absolute; inset:-12% -6%; z-index:3; opacity:0; transform:rotate(7deg); transition:opacity 1.4s ease; }
|
||||||
|
.starlake-scene .rainfall i { position:absolute; top:-14%; width:1px; height:10vh; background:linear-gradient(180deg, transparent, rgba(186,206,255,.34)); animation:sl-rain-fall linear infinite; }
|
||||||
|
.starlake-scene.rain .weather-veil, .starlake-scene.rain .rainfall { opacity:1; }
|
||||||
|
.starlake-scene.rain .glow { opacity:.6; }
|
||||||
|
@keyframes sl-rain-fall { to { transform:translateY(130vh); } }
|
||||||
|
|
||||||
|
/* 光均匀洒在整个光湖 */
|
||||||
|
.starlake-scene .glow { position:absolute; z-index:2; width:150vmax; height:150vmax; left:0; top:0;
|
||||||
|
mix-blend-mode:screen; transform:translate(-50%,-50%);
|
||||||
|
background:radial-gradient(circle, var(--glow-tint, rgba(255,236,180,.12)) 0%, transparent 42%); }
|
||||||
|
|
||||||
|
/* 光源点:天空最亮一粒星 */
|
||||||
|
.starlake-scene .source { position:absolute; z-index:8; width:0; height:0; cursor:pointer; will-change:left,top; pointer-events:auto; }
|
||||||
|
.starlake-scene .source .src-core { position:absolute; left:-8px; top:-8px; width:16px; height:16px; border-radius:50%;
|
||||||
|
background:radial-gradient(circle at 42% 38%, #fff, var(--src-mid, #ffe9a8) 58%, transparent 100%);
|
||||||
|
box-shadow:0 0 14px 3px var(--src-g1, rgba(255,236,180,.7)),
|
||||||
|
0 0 44px 14px var(--src-g2, rgba(255,220,140,.26)),
|
||||||
|
0 0 110px 42px var(--src-g3, rgba(255,220,140,.12));
|
||||||
|
animation:sl-src-breathe 7s ease-in-out infinite; }
|
||||||
|
@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 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 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 .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); }
|
||||||
|
.starlake-scene .hud .h-row { margin-top:7px; color:rgba(196,206,228,.62); font-size:12.5px; font-weight:600;
|
||||||
|
letter-spacing:.1em; font-variant-numeric:tabular-nums; text-shadow:0 1px 8px rgba(0,0,0,.7); }
|
||||||
|
.starlake-scene .hud .h-row em { font-style:normal; color:rgba(222,228,242,.88); margin-left:8px; }
|
||||||
|
.starlake-scene .hud .h-wx { margin-top:7px; color:rgba(222,228,242,.88); font-size:12.5px; font-weight:650;
|
||||||
|
letter-spacing:.14em; text-shadow:0 1px 8px rgba(0,0,0,.7); }
|
||||||
|
.starlake-scene .hud .h-wx i { font-style:normal; color:#f7ebc8; }
|
||||||
|
.starlake-scene .hud .h-link { display:inline-block; margin-top:13px; pointer-events:auto; cursor:pointer;
|
||||||
|
color:rgba(180,192,220,.4); font-size:12px; font-weight:650; letter-spacing:.26em;
|
||||||
|
background:transparent; border:0; border-bottom:1px solid rgba(202,216,255,.13);
|
||||||
|
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 .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:
|
||||||
|
radial-gradient(circle at 32% 26%, rgba(255,255,255,.95) 0%, rgba(255,255,255,.16) 22%, transparent 34%),
|
||||||
|
radial-gradient(circle at 50% 50%, var(--d-hi), var(--d-lo) 84%);
|
||||||
|
box-shadow:inset -5px -7px 12px rgba(0,0,0,.44), inset 3px 5px 10px rgba(255,255,255,.34),
|
||||||
|
0 0 var(--d-glow-r) var(--d-glow), 0 0 calc(var(--d-glow-r) * 1.9) var(--d-glow-soft);
|
||||||
|
animation:sl-core-breathe 6s ease-in-out infinite; }
|
||||||
|
@keyframes sl-core-breathe { 0%,100%{transform:translate(-50%,-50%) scale(1)} 50%{transform:translate(-50%,-50%) scale(1.045)} }
|
||||||
|
.starlake-scene .d-halo { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); border-radius:50%;
|
||||||
|
background:radial-gradient(circle, var(--d-glow-soft), transparent 66%); opacity:.8; }
|
||||||
|
.starlake-scene .d-water { display:block; margin:-4px auto 0; border-radius:50%;
|
||||||
|
background:radial-gradient(ellipse, var(--d-glow) 0%, transparent 68%);
|
||||||
|
filter:blur(5px); opacity:.5; animation:sl-mirror-breathe 6s ease-in-out infinite; }
|
||||||
|
.starlake-scene .d-mirror { display:block; margin:2px auto 0; border-radius:50%;
|
||||||
|
background:radial-gradient(circle, var(--d-glow), transparent 70%);
|
||||||
|
filter:blur(4px); opacity:.6; animation:sl-mirror-breathe 6s ease-in-out infinite; }
|
||||||
|
@keyframes sl-mirror-breathe { 0%,100%{opacity:.44;transform:scaleX(1)} 50%{opacity:.66;transform:scaleX(1.12)} }
|
||||||
|
.starlake-scene .d-ring { position:absolute; left:50%; top:50%; border-radius:50%; border:1px solid var(--d-ring-c);
|
||||||
|
transform:translate(-50%,-50%) rotateX(64deg); animation:sl-ring-turn 26s linear infinite; }
|
||||||
|
@keyframes sl-ring-turn { from{transform:translate(-50%,-50%) rotateX(64deg) rotate(0)} to{transform:translate(-50%,-50%) rotateX(64deg) rotate(360deg)} }
|
||||||
|
.starlake-scene .d-ring::before { content:''; position:absolute; top:-2.5px; left:50%; width:5px; height:5px;
|
||||||
|
margin-left:-2.5px; border-radius:50%; background:var(--d-hi); box-shadow:0 0 8px var(--d-glow); }
|
||||||
|
.starlake-scene .d-sat-arm { position:absolute; left:50%; top:50%; width:0; height:0; animation:sl-sat-orbit 17s linear infinite; }
|
||||||
|
@keyframes sl-sat-orbit { from{transform:rotate(0)} to{transform:rotate(360deg)} }
|
||||||
|
.starlake-scene .d-sat { position:absolute; left:var(--d-sat-r); top:0; width:7px; height:7px; margin:-3.5px 0 0 -3.5px;
|
||||||
|
border-radius:50%; background:radial-gradient(circle at 34% 30%, #fff, var(--d-lo)); box-shadow:0 0 8px var(--d-glow); }
|
||||||
|
.starlake-scene .d-dust { position:absolute; left:50%; top:50%; width:0; height:0; }
|
||||||
|
.starlake-scene .d-dust i { position:absolute; width:3px; height:3px; margin:-1.5px; border-radius:50%;
|
||||||
|
background:var(--d-hi); box-shadow:0 0 6px var(--d-glow); animation:sl-dust-fall var(--dust-t) linear infinite; }
|
||||||
|
@keyframes sl-dust-fall {
|
||||||
|
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;
|
||||||
|
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 .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;
|
||||||
|
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); }
|
||||||
|
.starlake-scene .era-foot { position:absolute; left:50%; bottom:21px; transform:translateX(-50%); color:rgba(180,192,220,.34);
|
||||||
|
font-size:10px; font-weight:650; letter-spacing:.28em; text-indent:.28em; }
|
||||||
|
|
||||||
|
.surface-pill { position:fixed; top:8px; left:18px; z-index:120; display:flex; gap:3px; padding:5px; border-radius:999px;
|
||||||
|
background:rgba(10,14,28,.55); backdrop-filter:blur(18px); -webkit-backdrop-filter:blur(18px);
|
||||||
|
border:1px solid rgba(255,255,255,.14); box-shadow:inset 0 1px 0 rgba(255,255,255,.12),0 10px 30px rgba(0,0,0,.35); }
|
||||||
|
.surface-pill button { border:0; border-radius:999px; padding:7px 18px; color:rgba(226,232,248,.6); background:transparent;
|
||||||
|
cursor:pointer; font:700 12px/1.2 "PingFang SC","Hiragino Sans GB","Source Han Sans SC",system-ui,sans-serif; letter-spacing:.14em; }
|
||||||
|
.surface-pill button:hover { color:rgba(238,242,252,.9); }
|
||||||
|
.surface-pill button.on { color:#f7ebc8; background:rgba(255,255,255,.11); }
|
||||||
|
.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 .d-core,.starlake-scene .d-ring,.starlake-scene .d-sat-arm,.starlake-scene .d-dust i,
|
||||||
|
.starlake-scene .d-mirror,.starlake-scene .d-water,.starlake-scene .source .src-core,
|
||||||
|
.starlake-scene .stars i,.starlake-scene .lakeStars i,.starlake-scene .rainfall i { animation-play-state:paused; }
|
||||||
|
.starlake-scene.awake .d-core,.starlake-scene.awake .d-ring,.starlake-scene.awake .d-sat-arm,.starlake-scene.awake .d-dust i,
|
||||||
|
.starlake-scene.awake .d-mirror,.starlake-scene.awake .d-water,.starlake-scene.awake .source .src-core,
|
||||||
|
.starlake-scene.awake .stars i,.starlake-scene.awake .lakeStars i,.starlake-scene.awake .rainfall i { animation-play-state:running; }
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.starlake-scene .d-core,.starlake-scene .d-ring,.starlake-scene .d-sat-arm,.starlake-scene .d-dust i,
|
||||||
|
.starlake-scene .d-mirror,.starlake-scene .d-water,.starlake-scene .source .src-core,
|
||||||
|
.starlake-scene .stars i,.starlake-scene .lakeStars i,.starlake-scene .rainfall i { animation:none; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,355 @@
|
||||||
|
/* ══ 传统工作台 · 玻璃地基(八式氛围)══
|
||||||
|
地基四层:深景光斑 → 玻璃本体 → 棱线高光/底部反影 → 颗粒噪点
|
||||||
|
风格=换氛围令牌,玻璃结构不动。作用域 .trad-surface,不污染语言世界轨。 */
|
||||||
|
|
||||||
|
.trad-surface {
|
||||||
|
position:fixed; inset:0; z-index:60; overflow:hidden;
|
||||||
|
font-family:"PingFang SC","Hiragino Sans GB","Source Han Sans SC",system-ui,sans-serif;
|
||||||
|
color:var(--ink); -webkit-font-smoothing:antialiased;
|
||||||
|
background:var(--bg-base); transition:background .5s ease;
|
||||||
|
}
|
||||||
|
.trad-surface.stack { overflow:auto; }
|
||||||
|
|
||||||
|
/* ── 八式氛围令牌 ── */
|
||||||
|
.trad-surface,
|
||||||
|
.official-world.surface-traditional {
|
||||||
|
--bg-base:#0a0e1f; --bg-mid:#131a38;
|
||||||
|
--glow-a:rgba(88,110,255,.5); --glow-b:rgba(190,90,255,.34); --glow-c:rgba(60,220,255,.3);
|
||||||
|
--accent:#8fb5ff; --accent2:#c9a3ff; --good:#7ce8c0; --warn:#ffc46b; --bad:#ff8fab;
|
||||||
|
--ink:#eef1ff; --ink-sub:rgba(238,241,255,.62); --ink-faint:rgba(238,241,255,.36);
|
||||||
|
--glass-hi:rgba(255,255,255,.1); --glass-lo:rgba(255,255,255,.028);
|
||||||
|
--glass-edge:rgba(255,255,255,.16); --glass-inner:rgba(255,255,255,.22);
|
||||||
|
}
|
||||||
|
.trad-surface[data-finish="champagne"],
|
||||||
|
.official-world.surface-traditional[data-finish="champagne"] {
|
||||||
|
--bg-base:#141008; --bg-mid:#241a0e;
|
||||||
|
--glow-a:rgba(255,176,80,.42); --glow-b:rgba(255,120,90,.3); --glow-c:rgba(255,220,150,.26);
|
||||||
|
--accent:#ffcf8a; --accent2:#ff9f7a; --good:#9fe3a8; --warn:#ffd45e; --bad:#ff7e8e;
|
||||||
|
--ink:#fdf4e6; --ink-sub:rgba(253,244,230,.62); --ink-faint:rgba(253,244,230,.36);
|
||||||
|
}
|
||||||
|
.trad-surface[data-finish="jade"],
|
||||||
|
.official-world.surface-traditional[data-finish="jade"] {
|
||||||
|
--bg-base:#071410; --bg-mid:#0d241c;
|
||||||
|
--glow-a:rgba(40,220,170,.4); --glow-b:rgba(70,160,255,.28); --glow-c:rgba(180,255,200,.22);
|
||||||
|
--accent:#6fe8c4; --accent2:#7fc4ff; --good:#7ce8c0; --warn:#ffd45e; --bad:#ff8fab;
|
||||||
|
--ink:#eafaf2; --ink-sub:rgba(234,250,242,.62); --ink-faint:rgba(234,250,242,.36);
|
||||||
|
}
|
||||||
|
.trad-surface[data-finish="porcelain"],
|
||||||
|
.official-world.surface-traditional[data-finish="porcelain"] {
|
||||||
|
--bg-base:#e8edf6; --bg-mid:#f4f7fd;
|
||||||
|
--glow-a:rgba(120,150,255,.4); --glow-b:rgba(200,130,255,.3); --glow-c:rgba(120,220,255,.32);
|
||||||
|
--accent:#4a6cf0; --accent2:#a05cf0; --good:#1fa06a; --warn:#d97a06; --bad:#e04a66;
|
||||||
|
--ink:#1b2340; --ink-sub:rgba(27,35,64,.6); --ink-faint:rgba(27,35,64,.38);
|
||||||
|
--glass-hi:rgba(255,255,255,.66); --glass-lo:rgba(255,255,255,.3);
|
||||||
|
--glass-edge:rgba(255,255,255,.8); --glass-inner:rgba(255,255,255,.9);
|
||||||
|
}
|
||||||
|
.trad-surface[data-finish="nebula"],
|
||||||
|
.official-world.surface-traditional[data-finish="nebula"] {
|
||||||
|
--bg-base:#170b20; --bg-mid:#2b1340;
|
||||||
|
--glow-a:rgba(255,110,190,.4); --glow-b:rgba(150,90,255,.4); --glow-c:rgba(90,140,255,.3);
|
||||||
|
--accent:#ff9ad5; --accent2:#b78aff; --good:#7ce8c0; --warn:#ffc46b; --bad:#ff7e8e;
|
||||||
|
--ink:#fdeef7; --ink-sub:rgba(253,238,247,.62); --ink-faint:rgba(253,238,247,.36);
|
||||||
|
--glass-hi:rgba(255,190,225,.11); --glass-lo:rgba(255,190,225,.03);
|
||||||
|
}
|
||||||
|
.trad-surface[data-finish="abyss"],
|
||||||
|
.official-world.surface-traditional[data-finish="abyss"] {
|
||||||
|
--bg-base:#04101c; --bg-mid:#082033;
|
||||||
|
--glow-a:rgba(0,150,255,.36); --glow-b:rgba(0,220,200,.26); --glow-c:rgba(60,120,255,.3);
|
||||||
|
--accent:#4fc3ff; --accent2:#39e6c8; --good:#6fe8b8; --warn:#ffd45e; --bad:#ff8fab;
|
||||||
|
--ink:#e8f6ff; --ink-sub:rgba(232,246,255,.62); --ink-faint:rgba(232,246,255,.36);
|
||||||
|
--glass-hi:rgba(160,220,255,.1); --glass-lo:rgba(160,220,255,.028);
|
||||||
|
}
|
||||||
|
.trad-surface[data-finish="cinnabar"],
|
||||||
|
.official-world.surface-traditional[data-finish="cinnabar"] {
|
||||||
|
--bg-base:#150a0a; --bg-mid:#2a1010;
|
||||||
|
--glow-a:rgba(255,70,60,.34); --glow-b:rgba(255,150,60,.24); --glow-c:rgba(255,90,120,.22);
|
||||||
|
--accent:#ff6b5e; --accent2:#ffb35e; --good:#8fe3a8; --warn:#ffd45e; --bad:#ff7e8e;
|
||||||
|
--ink:#fdf0ec; --ink-sub:rgba(253,240,236,.62); --ink-faint:rgba(253,240,236,.36);
|
||||||
|
--glass-hi:rgba(255,170,150,.09); --glass-lo:rgba(255,170,150,.026);
|
||||||
|
}
|
||||||
|
.trad-surface[data-finish="snow"],
|
||||||
|
.official-world.surface-traditional[data-finish="snow"] {
|
||||||
|
--bg-base:#e9f1f7; --bg-mid:#f6fbff;
|
||||||
|
--glow-a:rgba(90,170,255,.4); --glow-b:rgba(120,220,255,.34); --glow-c:rgba(160,140,255,.26);
|
||||||
|
--accent:#2f7fd6; --accent2:#6aa8ff; --good:#1fa06a; --warn:#d97a06; --bad:#e04a66;
|
||||||
|
--ink:#17273f; --ink-sub:rgba(23,39,63,.6); --ink-faint:rgba(23,39,63,.38);
|
||||||
|
--glass-hi:rgba(255,255,255,.7); --glass-lo:rgba(255,255,255,.32);
|
||||||
|
--glass-edge:rgba(255,255,255,.85); --glass-inner:rgba(255,255,255,.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 地基一层:深景 + 氛围光斑 + 星屑 ── */
|
||||||
|
.trad-surface .sky { position:absolute; inset:0; z-index:0; pointer-events:none;
|
||||||
|
background:radial-gradient(120% 90% at 50% -10%, var(--bg-mid), var(--bg-base) 70%); }
|
||||||
|
.trad-surface .sky::before { content:''; position:absolute; inset:0; filter:blur(30px);
|
||||||
|
background:
|
||||||
|
radial-gradient(46vw 34vh at 14% 6%, var(--glow-a), transparent 64%),
|
||||||
|
radial-gradient(40vw 36vh at 88% 18%, var(--glow-b), transparent 62%),
|
||||||
|
radial-gradient(50vw 30vh at 50% 108%, var(--glow-c), transparent 64%);
|
||||||
|
animation:trad-breathe 14s ease-in-out infinite alternate; }
|
||||||
|
@keyframes trad-breathe { from { opacity:.75; transform:scale(1); } to { opacity:1; transform:scale(1.06); } }
|
||||||
|
.trad-surface .sky::after { content:''; position:absolute; inset:0;
|
||||||
|
background-image:radial-gradient(1.4px 1.4px at 12% 22%, rgba(255,255,255,.5), transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 32% 12%, rgba(255,255,255,.42), transparent 100%),
|
||||||
|
radial-gradient(1.6px 1.6px at 58% 30%, rgba(255,255,255,.34), transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 74% 10%, rgba(255,255,255,.5), transparent 100%),
|
||||||
|
radial-gradient(1.3px 1.3px at 90% 42%, rgba(255,255,255,.3), transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 44% 52%, rgba(255,255,255,.26), transparent 100%); }
|
||||||
|
.trad-surface[data-finish="porcelain"] .sky::after, .trad-surface[data-finish="snow"] .sky::after { opacity:.5; }
|
||||||
|
/* 颗粒噪点 */
|
||||||
|
.trad-surface .grain { position:absolute; inset:0; z-index:1; pointer-events:none; opacity:.05; mix-blend-mode:overlay;
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
box-shadow:inset 0 1px 0 var(--glass-inner), 0 18px 50px rgba(3,6,18,.38); }
|
||||||
|
.trad-surface .glass::before { content:''; position:absolute; inset:0; border-radius:inherit; pointer-events:none;
|
||||||
|
background:linear-gradient(115deg, rgba(255,255,255,.14), transparent 34%); }
|
||||||
|
.trad-surface .glass::after { content:''; position:absolute; inset:0; border-radius:inherit; pointer-events:none;
|
||||||
|
background:radial-gradient(70% 30% at 50% 112%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 70%); }
|
||||||
|
.trad-surface[data-finish="porcelain"] .glass, .trad-surface[data-finish="snow"] .glass {
|
||||||
|
box-shadow:inset 0 1px 0 rgba(255,255,255,.9), 0 14px 40px rgba(70,90,150,.16); }
|
||||||
|
|
||||||
|
.trad-surface .lbl { color:var(--ink-faint); font-size:10.5px; font-weight:700; letter-spacing:.24em; }
|
||||||
|
.trad-surface .big { font-size:25px; font-weight:800; color:var(--ink); font-variant-numeric:tabular-nums; letter-spacing:.01em; }
|
||||||
|
.trad-surface .big em { font-style:normal; font-size:12px; color:var(--ink-sub); font-weight:650; margin-left:8px; letter-spacing:.04em; }
|
||||||
|
.trad-surface .tag { font-size:10.5px; font-weight:700; letter-spacing:.06em; padding:3.5px 10px; border-radius:999px; flex:none;
|
||||||
|
color:var(--tc, var(--accent)); background:color-mix(in srgb, var(--tc, var(--accent)) 13%, transparent);
|
||||||
|
border:1px solid color-mix(in srgb, var(--tc, var(--accent)) 26%, transparent); }
|
||||||
|
.trad-surface .bars { display:flex; align-items:flex-end; gap:5px; height:46px; margin-top:14px; }
|
||||||
|
.trad-surface .bars i { flex:1; border-radius:5px 5px 3px 3px;
|
||||||
|
background:linear-gradient(180deg, color-mix(in srgb, var(--accent2) 66%, transparent), color-mix(in srgb, var(--accent2) 24%, transparent)); }
|
||||||
|
.trad-surface .bars i.hot { background:linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 34%, transparent)); }
|
||||||
|
.trad-surface .row { display:flex; align-items:center; gap:11px; padding:11.5px 12px; border-radius:14px;
|
||||||
|
transition:background .18s ease; }
|
||||||
|
.trad-surface .row:hover { background:rgba(255,255,255,.06); }
|
||||||
|
.trad-surface[data-finish="porcelain"] .row:hover, .trad-surface[data-finish="snow"] .row:hover { background:rgba(255,255,255,.55); }
|
||||||
|
.trad-surface .row .dot { width:8px; height:8px; border-radius:50%; flex:none; box-shadow:0 0 10px 1px currentColor; }
|
||||||
|
.trad-surface .row .t { flex:1; min-width:0; }
|
||||||
|
.trad-surface .row .t b { display:block; font-size:13.5px; font-weight:700; color:var(--ink); letter-spacing:.02em; }
|
||||||
|
.trad-surface .row .t span { display:block; margin-top:2px; color:var(--ink-faint); font-size:11.5px; font-weight:550; }
|
||||||
|
.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 .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 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; }
|
||||||
|
.trad-surface .nav .tabs button:hover { color:var(--ink); }
|
||||||
|
.trad-surface .nav .tabs button.on { color:var(--ink); background:rgba(255,255,255,.1); }
|
||||||
|
.trad-surface[data-finish="porcelain"] .nav .tabs button.on, .trad-surface[data-finish="snow"] .nav .tabs button.on { background:rgba(255,255,255,.7); }
|
||||||
|
.trad-surface .nav .me { margin-left:auto; display:flex; align-items:center; gap:9px; color:var(--ink-sub); font-size:12px; font-weight:600; }
|
||||||
|
.trad-surface .nav .me i { width:27px; height:27px; border-radius:50%; display:grid; place-items:center; font-style:normal;
|
||||||
|
font-size:10px; font-weight:800; color:#0a0e1f;
|
||||||
|
background:linear-gradient(135deg, var(--accent), var(--accent2));
|
||||||
|
box-shadow:0 0 14px color-mix(in srgb, var(--accent) 50%, transparent); }
|
||||||
|
.trad-surface .nav .back { border:1px solid var(--glass-edge); background:transparent; color:var(--ink-sub);
|
||||||
|
font-size:11.5px; font-weight:700; letter-spacing:.08em; padding:6px 13px; border-radius:999px; cursor:pointer;
|
||||||
|
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 .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);
|
||||||
|
font-variant-numeric:tabular-nums; text-shadow:0 0 40px color-mix(in srgb, var(--accent) 32%, transparent); }
|
||||||
|
.trad-surface .hero .sub { margin-top:8px; color:var(--ink-sub); font-size:13px; font-weight:600; letter-spacing:.06em; }
|
||||||
|
.trad-surface .hero .sub b { color:var(--accent); font-weight:750; }
|
||||||
|
.trad-surface .hero .clock { margin-top:22px; font-size:15px; color:var(--ink-sub); font-weight:650;
|
||||||
|
letter-spacing:.14em; font-variant-numeric:tabular-nums; }
|
||||||
|
.trad-surface .hero .stats { margin-top:auto; display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
||||||
|
.trad-surface .hero .stat { border-radius:16px; padding:14px 16px;
|
||||||
|
background:rgba(255,255,255,.055); border:1px solid rgba(255,255,255,.09); }
|
||||||
|
.trad-surface[data-finish="porcelain"] .hero .stat, .trad-surface[data-finish="snow"] .hero .stat {
|
||||||
|
background:rgba(255,255,255,.5); border-color:rgba(255,255,255,.75); }
|
||||||
|
.trad-surface .hero .stat .big { font-size:21px; margin-top:7px; }
|
||||||
|
|
||||||
|
/* ── 世界广播 ── */
|
||||||
|
.trad-surface .broadcast { margin-top:24px; flex:1; min-height:0; display:flex; flex-direction:column; overflow:hidden; }
|
||||||
|
.trad-surface .broadcast .hd { display:flex; justify-content:space-between; align-items:center; }
|
||||||
|
.trad-surface .broadcast .live { display:flex; align-items:center; gap:6px; color:var(--good);
|
||||||
|
font-size:10px; font-weight:700; letter-spacing:.2em; }
|
||||||
|
.trad-surface .broadcast .live i { width:6px; height:6px; border-radius:50%; background:var(--good);
|
||||||
|
box-shadow:0 0 9px var(--good); animation:trad-pulse 2.2s ease-in-out infinite; }
|
||||||
|
@keyframes trad-pulse { 0%,100% { opacity:1; } 50% { opacity:.3; } }
|
||||||
|
.trad-surface .broadcast .feed { margin-top:11px; flex:1; min-height:0; overflow:hidden; display:flex; flex-direction:column; gap:7px; }
|
||||||
|
.trad-surface .b-item { display:flex; align-items:baseline; gap:10px; padding:10px 13px; border-radius:13px;
|
||||||
|
background:rgba(255,255,255,.045); border:1px solid rgba(255,255,255,.07); }
|
||||||
|
.trad-surface.awake .b-item { animation:trad-bin .55s ease; }
|
||||||
|
.trad-surface[data-finish="porcelain"] .b-item, .trad-surface[data-finish="snow"] .b-item {
|
||||||
|
background:rgba(255,255,255,.5); border-color:rgba(255,255,255,.7); }
|
||||||
|
@keyframes trad-bin { from { opacity:0; transform:translateY(-9px); } to { opacity:1; transform:none; } }
|
||||||
|
.trad-surface .b-item .d { flex:none; font-size:11px; font-weight:750; letter-spacing:.08em; color:var(--bc); }
|
||||||
|
.trad-surface .b-item .d::before { content:''; display:inline-block; width:7px; height:7px; border-radius:50%;
|
||||||
|
background:var(--bc); box-shadow:0 0 8px var(--bc); margin-right:7px; vertical-align:1px; }
|
||||||
|
.trad-surface .b-item .m { flex:1; min-width:0; color:var(--ink-sub); font-size:12.5px; font-weight:550;
|
||||||
|
letter-spacing:.02em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||||
|
.trad-surface .b-item .tm { flex:none; color:var(--ink-faint); font-size:10.5px; font-weight:650;
|
||||||
|
letter-spacing:.08em; font-variant-numeric:tabular-nums; }
|
||||||
|
|
||||||
|
.trad-surface .card { padding:16px; display:flex; flex-direction:column; min-height:0; overflow:hidden; }
|
||||||
|
.trad-surface .card .hd { display:flex; justify-content:space-between; align-items:center; padding:2px 12px 10px; }
|
||||||
|
.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 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;
|
||||||
|
letter-spacing:.08em; padding:6px 13px; border-radius:999px; cursor:pointer;
|
||||||
|
transition:color .18s ease, background .18s ease; }
|
||||||
|
.trad-surface .finisher button:hover { color:var(--ink); }
|
||||||
|
.trad-surface .finisher button.on { color:var(--ink); background:rgba(255,255,255,.12); }
|
||||||
|
.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); }
|
||||||
|
|
||||||
|
/* ── 语言世界侧入口按钮(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;
|
||||||
|
cursor:pointer; opacity:.75; transition:opacity .18s ease; }
|
||||||
|
.world-footer .surface-switch:hover { opacity:1; }
|
||||||
|
|
||||||
|
/* 轻量运行:只有用户正在指向或操作软件时,氛围与广播才继续动。 */
|
||||||
|
.trad-surface .sky::before,
|
||||||
|
.trad-surface .broadcast .live i,
|
||||||
|
.trad-surface .b-item { animation-play-state:paused; }
|
||||||
|
.trad-surface.awake .sky::before,
|
||||||
|
.trad-surface.awake .broadcast .live i,
|
||||||
|
.trad-surface.awake .b-item { animation-play-state:running; }
|
||||||
|
|
||||||
|
/* 传统轨不是单页皮肤。下游知识库、频道、教育与网文工作台统一继承同一组八式令牌。 */
|
||||||
|
.official-world.surface-traditional {
|
||||||
|
--primitive-bg-a:var(--bg-base);
|
||||||
|
--primitive-bg-b:color-mix(in srgb,var(--bg-base) 72%,var(--bg-mid));
|
||||||
|
--primitive-bg-c:var(--bg-mid);
|
||||||
|
--primitive-bg-d:color-mix(in srgb,var(--bg-base) 86%,#02040b);
|
||||||
|
--surface-sky:var(--bg-base);
|
||||||
|
--surface-horizon:var(--bg-mid);
|
||||||
|
--surface-depth:color-mix(in srgb,var(--bg-base) 90%,#02040b);
|
||||||
|
--surface-panel:color-mix(in srgb,var(--glass-hi) 58%,var(--bg-mid));
|
||||||
|
--surface-strong:color-mix(in srgb,var(--glass-hi) 72%,var(--bg-mid));
|
||||||
|
--content-primary:var(--ink);
|
||||||
|
--content-secondary:var(--ink-sub);
|
||||||
|
--content-muted:var(--ink-faint);
|
||||||
|
--content-faint:var(--ink-faint);
|
||||||
|
--text-primary:var(--ink);
|
||||||
|
--text-secondary:var(--ink-sub);
|
||||||
|
--text-muted:var(--ink-faint);
|
||||||
|
--panel-edge:var(--glass-edge);
|
||||||
|
--panel-bg:color-mix(in srgb,var(--glass-hi) 54%,var(--bg-base));
|
||||||
|
--primitive-glass:var(--glass-hi);
|
||||||
|
--primitive-glass-edge:var(--glass-edge);
|
||||||
|
--button-primary-bg:var(--accent);
|
||||||
|
--button-primary-text:var(--bg-base);
|
||||||
|
--button-secondary-bg:var(--glass-hi);
|
||||||
|
--button-secondary-text:var(--ink);
|
||||||
|
--button-secondary-edge:var(--glass-edge);
|
||||||
|
--accent-light:var(--accent);
|
||||||
|
--primitive-cool-glow:var(--accent);
|
||||||
|
--primitive-warm-glow:var(--accent2);
|
||||||
|
--state-ready:var(--good);
|
||||||
|
--state-warning:var(--warn);
|
||||||
|
--state-danger:var(--bad);
|
||||||
|
position:relative;
|
||||||
|
z-index:0;
|
||||||
|
color:var(--ink);
|
||||||
|
background:
|
||||||
|
radial-gradient(46vw 34vh at 14% 6%,var(--glow-a),transparent 64%),
|
||||||
|
radial-gradient(40vw 36vh at 88% 18%,var(--glow-b),transparent 62%),
|
||||||
|
radial-gradient(50vw 30vh at 50% 108%,var(--glow-c),transparent 64%),
|
||||||
|
radial-gradient(120% 90% at 50% -10%,var(--bg-mid),var(--bg-base) 70%);
|
||||||
|
}
|
||||||
|
.official-world.surface-traditional .world-titlebar,
|
||||||
|
.official-world.surface-traditional .world-tool,
|
||||||
|
.official-world.surface-traditional .education-workspace-world,
|
||||||
|
.official-world.surface-traditional .composition-workspace-world,
|
||||||
|
.official-world.surface-traditional .wn-workspace,
|
||||||
|
.official-world.surface-traditional .mobile-sync-world,
|
||||||
|
.official-world.surface-traditional .persona-body-world {
|
||||||
|
background:linear-gradient(165deg,var(--glass-hi),var(--glass-lo) 55%);
|
||||||
|
border-color:var(--glass-edge);
|
||||||
|
box-shadow:inset 0 1px 0 var(--glass-inner),0 18px 50px rgba(3,6,18,.28);
|
||||||
|
-webkit-backdrop-filter:blur(26px) saturate(1.5);
|
||||||
|
backdrop-filter:blur(26px) saturate(1.5);
|
||||||
|
}
|
||||||
|
.official-world.surface-traditional .world-scene,
|
||||||
|
.official-world.surface-traditional .tool-projection,
|
||||||
|
.official-world.surface-traditional .content-page,
|
||||||
|
.official-world.surface-traditional .plain-panel,
|
||||||
|
.official-world.surface-traditional .knowledge-workspace,
|
||||||
|
.official-world.surface-traditional .channel-workbench,
|
||||||
|
.official-world.surface-traditional .wn-workspace,
|
||||||
|
.official-world.surface-traditional .education-workspace {
|
||||||
|
color:var(--ink);
|
||||||
|
}
|
||||||
|
.official-world.surface-traditional .plain-panel,
|
||||||
|
.official-world.surface-traditional .content-page,
|
||||||
|
.official-world.surface-traditional .education-panel,
|
||||||
|
.official-world.surface-traditional .wn-panel,
|
||||||
|
.official-world.surface-traditional .workbench-panel {
|
||||||
|
background:linear-gradient(165deg,var(--glass-hi),var(--glass-lo) 55%);
|
||||||
|
border-color:var(--glass-edge);
|
||||||
|
}
|
||||||
|
.official-world.surface-traditional .world-pool {
|
||||||
|
border:1px solid var(--glass-edge);
|
||||||
|
border-radius:18px;
|
||||||
|
background:linear-gradient(165deg,var(--glass-hi),var(--glass-lo) 55%);
|
||||||
|
box-shadow:inset 0 1px 0 var(--glass-inner),0 14px 38px rgba(3,6,18,.24);
|
||||||
|
}
|
||||||
|
.official-world.surface-traditional .world-pool .pool-bay { display:none; }
|
||||||
|
.official-world.surface-traditional .world-pool .pool-label { top:50%; transform:translate(-50%,-50%); gap:7px; }
|
||||||
|
.official-world.surface-traditional .world-pool.open {
|
||||||
|
border-color:color-mix(in srgb,var(--accent) 60%,var(--glass-edge));
|
||||||
|
box-shadow:inset 0 1px 0 var(--glass-inner),0 0 34px color-mix(in srgb,var(--accent) 18%,transparent);
|
||||||
|
}
|
||||||
|
.official-world.qoder-home .world-footer { display:none; }
|
||||||
|
.official-world.surface-traditional[data-tone="light"] .world-titlebar,
|
||||||
|
.official-world.surface-traditional[data-tone="light"] .tool-worldbar {
|
||||||
|
box-shadow:0 1px 0 rgba(27,35,64,.1);
|
||||||
|
}
|
||||||
|
.official-world.surface-traditional:not(.motion-awake) .world-titlebar,
|
||||||
|
.official-world.surface-traditional:not(.motion-awake) .world-tool,
|
||||||
|
.official-world.surface-traditional:not(.motion-awake) .education-workspace-world,
|
||||||
|
.official-world.surface-traditional:not(.motion-awake) .composition-workspace-world,
|
||||||
|
.official-world.surface-traditional:not(.motion-awake) .wn-workspace,
|
||||||
|
.official-world.surface-traditional:not(.motion-awake) .mobile-sync-world,
|
||||||
|
.official-world.surface-traditional:not(.motion-awake) .persona-body-world {
|
||||||
|
-webkit-backdrop-filter:none;
|
||||||
|
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;
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.trad-surface .sky::before { animation:none; }
|
||||||
|
.trad-surface .broadcast .live i { animation:none; }
|
||||||
|
.trad-surface .b-item { animation:none; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import type { FinishId } from './TraditionalSurface'
|
||||||
|
|
||||||
|
interface Rgb { r: number; g: number; b: number }
|
||||||
|
interface FinishProfile { background: string; foreground: string; tone: 'dark' | 'light' }
|
||||||
|
|
||||||
|
export const FINISH_PROFILES: Record<FinishId, FinishProfile> = {
|
||||||
|
aurora: { background: '#0a0e1f', foreground: '#eef1ff', tone: 'dark' },
|
||||||
|
nebula: { background: '#170b20', foreground: '#fdeef7', tone: 'dark' },
|
||||||
|
abyss: { background: '#04101c', foreground: '#e8f6ff', tone: 'dark' },
|
||||||
|
jade: { background: '#071410', foreground: '#eafaf2', tone: 'dark' },
|
||||||
|
cinnabar: { background: '#150a0a', foreground: '#fdf0ec', tone: 'dark' },
|
||||||
|
champagne: { background: '#141008', foreground: '#fdf4e6', tone: 'dark' },
|
||||||
|
porcelain: { background: '#e8edf6', foreground: '#1b2340', tone: 'light' },
|
||||||
|
snow: { background: '#e9f1f7', foreground: '#17273f', tone: 'light' },
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseHex(value: string): Rgb {
|
||||||
|
const normalized = value.replace('#', '')
|
||||||
|
return { r: parseInt(normalized.slice(0, 2), 16), g: parseInt(normalized.slice(2, 4), 16), b: parseInt(normalized.slice(4, 6), 16) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function luminance({ r, g, b }: Rgb) {
|
||||||
|
const channel = (value: number) => {
|
||||||
|
const normalized = value / 255
|
||||||
|
return normalized <= .03928 ? normalized / 12.92 : Math.pow((normalized + .055) / 1.055, 2.4)
|
||||||
|
}
|
||||||
|
return .2126 * channel(r) + .7152 * channel(g) + .0722 * channel(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function contrastRatio(foreground: string, background: string) {
|
||||||
|
const a = luminance(parseHex(foreground))
|
||||||
|
const b = luminance(parseHex(background))
|
||||||
|
return (Math.max(a, b) + .05) / (Math.min(a, b) + .05)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveVisualBalance(finish: FinishId, phase?: string, weather?: string) {
|
||||||
|
const profile = FINISH_PROFILES[finish]
|
||||||
|
return {
|
||||||
|
tone: profile.tone,
|
||||||
|
contrast: contrastRatio(profile.foreground, profile.background),
|
||||||
|
phase: phase?.toLowerCase() || 'time-pending',
|
||||||
|
weather: weather?.toLowerCase() || 'unavailable',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -571,6 +571,7 @@ export function WebNovelWorkspace({ onBack }: { onBack: () => void }) {
|
||||||
dirtyRef.current = false
|
dirtyRef.current = false
|
||||||
const next = await invoke<WorkDetail>('read_web_novel_work', { input: { workId: detail.work.workId } })
|
const next = await invoke<WorkDetail>('read_web_novel_work', { input: { workId: detail.work.workId } })
|
||||||
setDetail(next)
|
setDetail(next)
|
||||||
|
await refreshSnapshot(detail.work.workId)
|
||||||
setMode('writing')
|
setMode('writing')
|
||||||
setChapterCreate(null)
|
setChapterCreate(null)
|
||||||
setMessage('章节已创建,正文会自动保存并生成版本。')
|
setMessage('章节已创建,正文会自动保存并生成版本。')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue