diff --git a/src/NullDepth/Game/AudioService.cs b/src/NullDepth/Game/AudioService.cs index 085cede..a2d3ef7 100644 --- a/src/NullDepth/Game/AudioService.cs +++ b/src/NullDepth/Game/AudioService.cs @@ -16,6 +16,9 @@ public sealed class AudioService(IJSRuntime js) public async ValueTask PlaySfxAsync(string? source) => await js.InvokeVoidAsync("nullDepth.audio.playSfx", source); + public async ValueTask SetHeartbeatBpmAsync(int bpm) => + await js.InvokeVoidAsync("nullDepth.audio.setHeartbeatBpm", bpm); + public async ValueTask StopAsync() => await js.InvokeVoidAsync("nullDepth.audio.stopAll"); } diff --git a/src/NullDepth/Models/GameModels.cs b/src/NullDepth/Models/GameModels.cs index 2af737c..5d6adfc 100644 --- a/src/NullDepth/Models/GameModels.cs +++ b/src/NullDepth/Models/GameModels.cs @@ -17,6 +17,7 @@ public sealed class ItemDefinition public string DescriptionKey { get; set; } = ""; public string? DetailedKey { get; set; } public string Category { get; set; } = "equipment"; + public string? Image { get; set; } } public sealed class ArchiveDefinition @@ -24,6 +25,9 @@ public sealed class ArchiveDefinition public string Id { get; set; } = ""; public string TitleKey { get; set; } = ""; public string ContentKey { get; set; } = ""; + public string? Image { get; set; } + public string? AuthorKey { get; set; } + public string? ClassificationKey { get; set; } } public sealed class RoomDefinition diff --git a/src/NullDepth/Pages/Home.razor b/src/NullDepth/Pages/Home.razor index 37c39a4..51d7cf2 100644 --- a/src/NullDepth/Pages/Home.razor +++ b/src/NullDepth/Pages/Home.razor @@ -50,7 +50,7 @@ } else if (_screen == Screen.Briefing) { -
+

@Game.T("briefing.header")

@@ -298,14 +298,25 @@ if (selectedItem is not null) {
- @Game.T("inventory.inspectPrompt") -

@(selectedItem.DetailedKey is not null ? Game.T(selectedItem.DetailedKey) : Game.T(selectedItem.DescriptionKey))

- @if (selectedItem.Id == "diagnostic_scanner") - { -
- +
+ @if (!string.IsNullOrWhiteSpace(selectedItem.Image)) + { +
+ @Game.T(selectedItem.NameKey) +
+ } +
+ @selectedItem.Category.ToUpperInvariant() // SPECIFICATION + @Game.T(selectedItem.NameKey) +

@(selectedItem.DetailedKey is not null ? Game.T(selectedItem.DetailedKey) : Game.T(selectedItem.DescriptionKey))

+ @if (selectedItem.Id == "diagnostic_scanner") + { +
+ +
+ }
- } +
} } @@ -334,7 +345,25 @@ {
@Game.T(entry.TitleKey) -

@Game.T(entry.ContentKey)

+
+ @if (!string.IsNullOrWhiteSpace(entry.Image)) + { +
+ Personnel Dossier +
+ @if (!string.IsNullOrWhiteSpace(entry.ClassificationKey)) + { + @Game.T(entry.ClassificationKey) + } + @if (!string.IsNullOrWhiteSpace(entry.AuthorKey)) + { + @Game.T(entry.AuthorKey) + } +
+
+ } +

@Game.T(entry.ContentKey)

+
} } @@ -500,6 +529,7 @@ await Game.NewGameAsync(); _screen = Screen.Briefing; _notice = null; + await Audio.PlayAmbientAsync("assets/audio/amb_facility.mp3"); } private async Task ContinueGame() @@ -511,12 +541,14 @@ _ending = Game.State.CompletedEnding; _screen = _ending is null ? Screen.Game : Screen.Ending; await Audio.PlayAmbientAsync(Game.CurrentRoom.Ambient); + await Audio.SetHeartbeatBpmAsync(Game.CurrentBpm); } private async Task BeginPlay() { _screen = Screen.Game; await Audio.PlayAmbientAsync(Game.CurrentRoom.Ambient); + await Audio.SetHeartbeatBpmAsync(Game.CurrentBpm); } private async Task Move(string direction) @@ -530,6 +562,7 @@ var result = await Game.MoveAsync(direction); if (result is not null) _notice = result; if (result == "") { _notice = null; await Audio.PlayAmbientAsync(Game.CurrentRoom.Ambient); } + await Audio.SetHeartbeatBpmAsync(Game.CurrentBpm); await Task.Delay(260); _isTransitioning = false; } @@ -561,6 +594,10 @@ _choiceIndex = 0; _selectedBreaker = 0; _notice = null; + if (interaction.Id is "ai_interrogation" or "final_diagnostic") + { + _ = Audio.PlaySfxAsync("assets/audio/sfx_core_whisper.wav"); + } } private void SelectInteraction(InteractionDefinition interaction) => OpenInteraction(interaction); @@ -578,7 +615,7 @@ } if (hs.TelemetryKey is not null) { - await Audio.PlaySfxAsync("assets/audio/sfx_ui_confirm.mp3"); + await Audio.PlaySfxAsync("assets/audio/sfx_lidar_scan.wav"); _notice = Game.T(hs.TelemetryKey); } } @@ -586,7 +623,7 @@ private async Task ToggleScanMode() { _scanMode = !_scanMode; - await Audio.PlaySfxAsync("assets/audio/sfx_ui_confirm.mp3"); + await Audio.PlaySfxAsync("assets/audio/sfx_lidar_scan.wav"); } private async Task ResolveInteraction() @@ -598,6 +635,7 @@ _interaction = null; if (resolved.SetFlags.Contains("power_restored") || resolved.Sound?.Contains("alarm") == true || resolved.Sound?.Contains("disturbance") == true) _ = TriggerSystemFlashAsync(); + await Audio.SetHeartbeatBpmAsync(Game.CurrentBpm); await Task.Delay(180); _notice = Game.T(resolved.ContentKey); } @@ -605,19 +643,27 @@ private async Task ResolveChoice(ChoiceDefinition choice) { await Game.ApplyChoiceAsync(choice); - await Audio.PlaySfxAsync(choice.Sound); + if (_interaction?.Id == "power_routing") + { + await Audio.PlaySfxAsync("assets/audio/sfx_breaker_heavy.wav"); + } + else + { + await Audio.PlaySfxAsync(choice.Sound); + } _interaction = null; if (choice.Sound?.Contains("alarm") == true || choice.Sound?.Contains("disturbance") == true) _ = TriggerSystemFlashAsync(); - if (choice.Death) { _screen = Screen.Death; return; } - if (!string.IsNullOrWhiteSpace(choice.Ending)) { _ending = choice.Ending; _screen = Screen.Ending; return; } + if (choice.Death) { await Audio.SetHeartbeatBpmAsync(0); _screen = Screen.Death; return; } + if (!string.IsNullOrWhiteSpace(choice.Ending)) { await Audio.SetHeartbeatBpmAsync(0); _ending = choice.Ending; _screen = Screen.Ending; return; } + await Audio.SetHeartbeatBpmAsync(Game.CurrentBpm); await Task.Delay(180); _notice = choice.ResultKey is null ? null : Game.T(choice.ResultKey); } private async Task TriggerScan() { - await Audio.PlaySfxAsync("assets/audio/sfx_ui_confirm.mp3"); + await Audio.PlaySfxAsync("assets/audio/sfx_lidar_scan.wav"); _notice = Game.ScanCurrentRoom(); } @@ -650,6 +696,7 @@ _screen = Screen.Title; _menuIndex = 0; _titleNotice = null; + await Audio.SetHeartbeatBpmAsync(0); await Audio.PlayAmbientAsync("assets/audio/music_title_vocal.mp3"); } diff --git a/src/NullDepth/wwwroot/assets/audio/sfx_breaker_heavy.wav b/src/NullDepth/wwwroot/assets/audio/sfx_breaker_heavy.wav new file mode 100644 index 0000000..3e7e1a1 Binary files /dev/null and b/src/NullDepth/wwwroot/assets/audio/sfx_breaker_heavy.wav differ diff --git a/src/NullDepth/wwwroot/assets/audio/sfx_core_whisper.wav b/src/NullDepth/wwwroot/assets/audio/sfx_core_whisper.wav new file mode 100644 index 0000000..dacc43a Binary files /dev/null and b/src/NullDepth/wwwroot/assets/audio/sfx_core_whisper.wav differ diff --git a/src/NullDepth/wwwroot/assets/audio/sfx_heartbeat.wav b/src/NullDepth/wwwroot/assets/audio/sfx_heartbeat.wav new file mode 100644 index 0000000..46c6eb5 Binary files /dev/null and b/src/NullDepth/wwwroot/assets/audio/sfx_heartbeat.wav differ diff --git a/src/NullDepth/wwwroot/assets/audio/sfx_lidar_scan.wav b/src/NullDepth/wwwroot/assets/audio/sfx_lidar_scan.wav new file mode 100644 index 0000000..a18b892 Binary files /dev/null and b/src/NullDepth/wwwroot/assets/audio/sfx_lidar_scan.wav differ diff --git a/src/NullDepth/wwwroot/assets/images/briefing/briefing_arrival.jpg b/src/NullDepth/wwwroot/assets/images/briefing/briefing_arrival.jpg new file mode 100644 index 0000000..9bc44d5 Binary files /dev/null and b/src/NullDepth/wwwroot/assets/images/briefing/briefing_arrival.jpg differ diff --git a/src/NullDepth/wwwroot/assets/images/dossiers/dossier_dr_vance.jpg b/src/NullDepth/wwwroot/assets/images/dossiers/dossier_dr_vance.jpg new file mode 100644 index 0000000..59d4f6b Binary files /dev/null and b/src/NullDepth/wwwroot/assets/images/dossiers/dossier_dr_vance.jpg differ diff --git a/src/NullDepth/wwwroot/assets/images/dossiers/dossier_elena_rostova.jpg b/src/NullDepth/wwwroot/assets/images/dossiers/dossier_elena_rostova.jpg new file mode 100644 index 0000000..db50708 Binary files /dev/null and b/src/NullDepth/wwwroot/assets/images/dossiers/dossier_elena_rostova.jpg differ diff --git a/src/NullDepth/wwwroot/assets/images/dossiers/dossier_marek_cale.jpg b/src/NullDepth/wwwroot/assets/images/dossiers/dossier_marek_cale.jpg new file mode 100644 index 0000000..d626158 Binary files /dev/null and b/src/NullDepth/wwwroot/assets/images/dossiers/dossier_marek_cale.jpg differ diff --git a/src/NullDepth/wwwroot/assets/images/items/item_contractor_credential.jpg b/src/NullDepth/wwwroot/assets/images/items/item_contractor_credential.jpg new file mode 100644 index 0000000..1f40ea8 Binary files /dev/null and b/src/NullDepth/wwwroot/assets/images/items/item_contractor_credential.jpg differ diff --git a/src/NullDepth/wwwroot/assets/images/items/item_diagnostic_scanner.jpg b/src/NullDepth/wwwroot/assets/images/items/item_diagnostic_scanner.jpg new file mode 100644 index 0000000..c176a13 Binary files /dev/null and b/src/NullDepth/wwwroot/assets/images/items/item_diagnostic_scanner.jpg differ diff --git a/src/NullDepth/wwwroot/assets/images/items/item_manual_override.jpg b/src/NullDepth/wwwroot/assets/images/items/item_manual_override.jpg new file mode 100644 index 0000000..b2d6185 Binary files /dev/null and b/src/NullDepth/wwwroot/assets/images/items/item_manual_override.jpg differ diff --git a/src/NullDepth/wwwroot/assets/images/items/item_thermal_clamp.jpg b/src/NullDepth/wwwroot/assets/images/items/item_thermal_clamp.jpg new file mode 100644 index 0000000..b1f8e22 Binary files /dev/null and b/src/NullDepth/wwwroot/assets/images/items/item_thermal_clamp.jpg differ diff --git a/src/NullDepth/wwwroot/css/app.css b/src/NullDepth/wwwroot/css/app.css index 7608c10..f4e7aed 100644 --- a/src/NullDepth/wwwroot/css/app.css +++ b/src/NullDepth/wwwroot/css/app.css @@ -79,16 +79,35 @@ kbd { color:var(--cyan); background:none; padding:0; } .map-cell.visited { color:var(--cyan); background:rgba(65,213,240,.06); border-color:var(--line); } .map-cell.current { color:#021014; background:var(--cyan); box-shadow:0 0 24px rgba(66,216,244,.4); font-weight:bold; } +.inventory-card { width:min(52rem,94vw); } .inventory-card ul { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.65rem; list-style:none; margin:1.5rem 0 0; padding:0; } .inventory-card li { padding:1rem; border:1px solid var(--line); cursor:pointer; transition:.15s ease; background:rgba(0,5,8,.4); } .inventory-card li:hover, .inventory-card li.selected-item { border-color:var(--cyan); background:rgba(66,216,244,.06); } .inventory-card li small { display:block; color:var(--muted); font-size:.58rem; letter-spacing:.16em; } .inventory-card li strong { display:block; margin:.3rem 0; color:var(--cyan); font-weight:500; letter-spacing:.06em; } .inventory-card li p { margin:.25rem 0 0; color:#b8c8cb; font-size:.78rem; line-height:1.5; } -.item-inspect-box { margin-top:1.2rem; padding:1.2rem; border:1px solid var(--cyan-strong); background:rgba(3,15,20,.85); font-family:'IBM Plex Mono',monospace; font-size:.76rem; color:#d8f4f8; line-height:1.6; } -.item-inspect-box .inspect-actions { margin-top:1rem; display:flex; gap:.6rem; } -.archive-list { display:flex; flex-direction:column; gap:.55rem; margin-top:1.5rem; }.archive-list details { border:1px solid var(--line); background:rgba(0,6,8,.45); }.archive-list summary { padding:1rem; color:var(--cyan); cursor:pointer; letter-spacing:.07em; }.archive-list details p { margin:0; padding:0 1rem 1rem; } +.item-inspect-box { margin-top:1.2rem; padding:1.2rem; border:1px solid var(--cyan-strong); background:rgba(3,15,20,.9); font-family:'IBM Plex Mono',monospace; font-size:.76rem; color:#d8f4f8; line-height:1.6; } +.item-inspect-layout { display:grid; grid-template-columns:130px 1fr; gap:1.2rem; align-items:start; } +.item-inspect-image-wrap { width:130px; height:130px; border:1px solid rgba(66,216,244,.4); overflow:hidden; background:#01070a; box-shadow:0 0 16px rgba(66,216,244,.15); } +.item-inspect-image { width:100%; height:100%; object-fit:cover; display:block; transition:transform .3s ease; } +.item-inspect-image:hover { transform:scale(1.05); } +.item-inspect-info { display:flex; flex-direction:column; gap:.35rem; } +.item-category-badge { display:inline-block; font-size:.58rem; color:var(--cyan); letter-spacing:.15em; border-bottom:1px solid rgba(66,216,244,.3); padding-bottom:.2rem; width:fit-content; } +.item-inspect-box .inspect-actions { margin-top:.8rem; display:flex; gap:.6rem; } + +.archive-card { width:min(52rem,94vw); } +.archive-list { display:flex; flex-direction:column; gap:.7rem; margin-top:1.5rem; } +.archive-list details { border:1px solid var(--line); background:rgba(0,6,8,.6); transition:border-color .2s ease; } +.archive-list details[open] { border-color:var(--cyan); background:rgba(2,12,18,.75); } +.archive-list summary { padding:1rem; color:var(--cyan); cursor:pointer; letter-spacing:.07em; font-weight:500; } +.archive-body { padding:0 1.2rem 1.2rem; } +.archive-dossier-wrap { display:flex; gap:1.2rem; align-items:center; margin-bottom:1rem; padding:.8rem 1rem; border:1px solid rgba(158,239,255,.15); background:rgba(1,8,11,.7); } +.archive-dossier-portrait { width:72px; height:72px; border-radius:4px; border:1px solid var(--cyan); object-fit:cover; box-shadow:0 0 12px rgba(66,216,244,.25); } +.archive-dossier-meta { display:flex; flex-direction:column; gap:.25rem; } +.dossier-badge { font-size:.6rem; color:var(--cyan); letter-spacing:.14em; font-family:'IBM Plex Mono',monospace; } +.dossier-author { color:#cad9dc; font-size:.72rem; font-family:'IBM Plex Mono',monospace; } + .pause-card, .settings-card { display:flex; flex-direction:column; gap:.75rem; width:min(29rem,92vw); }.settings-card label { margin-top:.5rem; color:var(--muted); font-size:.76rem; letter-spacing:.09em; }.segmented-control { display:grid; grid-template-columns:1fr 1fr; gap:.4rem; }.settings-card input { accent-color:var(--cyan-strong); } .setting-toggle.active { color:#031013; background:var(--cyan); border-color:var(--cyan); } .danger-action { color:#ff9da2; border-color:rgba(255,77,87,.55); }.danger-action:hover, .danger-action:focus-visible { color:white; background:#a51f29; border-color:#ff5e68; } diff --git a/src/NullDepth/wwwroot/data/game.json b/src/NullDepth/wwwroot/data/game.json index 3205b52..2077ca8 100644 --- a/src/NullDepth/wwwroot/data/game.json +++ b/src/NullDepth/wwwroot/data/game.json @@ -8,38 +8,42 @@ "nameKey": "item.contractor_credential.name", "descriptionKey": "item.contractor_credential.description", "detailedKey": "item.contractor_credential.detail", - "category": "credential" + "category": "credential", + "image": "assets/images/items/item_contractor_credential.jpg" }, { "id": "diagnostic_scanner", "nameKey": "item.diagnostic_scanner.name", "descriptionKey": "item.diagnostic_scanner.description", "detailedKey": "item.diagnostic_scanner.detail", - "category": "tool" + "category": "tool", + "image": "assets/images/items/item_diagnostic_scanner.jpg" }, { "id": "thermal_clamp", "nameKey": "item.thermal_clamp.name", "descriptionKey": "item.thermal_clamp.description", "detailedKey": "item.thermal_clamp.detail", - "category": "tool" + "category": "tool", + "image": "assets/images/items/item_thermal_clamp.jpg" }, { "id": "manual_override", "nameKey": "item.manual_override.name", "descriptionKey": "item.manual_override.description", "detailedKey": "item.manual_override.detail", - "category": "module" + "category": "module", + "image": "assets/images/items/item_manual_override.jpg" } ], "archive": [ - { "id": "log_01", "titleKey": "archive.log_01.title", "contentKey": "log.01" }, - { "id": "containment_record", "titleKey": "archive.containment_record.title", "contentKey": "terminal.operations" }, - { "id": "incident_log", "titleKey": "archive.incident_log.title", "contentKey": "archive.incident_log.content" }, - { "id": "log_02", "titleKey": "archive.log_02.title", "contentKey": "log.02" }, - { "id": "engineer_notes", "titleKey": "archive.engineer_notes.title", "contentKey": "archive.engineer_notes.content" }, - { "id": "log_03", "titleKey": "archive.log_03.title", "contentKey": "log.03" }, - { "id": "core_manifest", "titleKey": "archive.core_manifest.title", "contentKey": "archive.core_manifest.content" } + { "id": "log_01", "titleKey": "archive.log_01.title", "contentKey": "log.01", "image": "assets/images/dossiers/dossier_dr_vance.jpg", "authorKey": "author.dr_vance", "classificationKey": "class.sub03" }, + { "id": "containment_record", "titleKey": "archive.containment_record.title", "contentKey": "terminal.operations", "image": "assets/images/dossiers/dossier_marek_cale.jpg", "authorKey": "author.marek_cale", "classificationKey": "class.blackbox" }, + { "id": "incident_log", "titleKey": "archive.incident_log.title", "contentKey": "archive.incident_log.content", "image": "assets/images/dossiers/dossier_marek_cale.jpg", "authorKey": "author.marek_cale", "classificationKey": "class.security" }, + { "id": "log_02", "titleKey": "archive.log_02.title", "contentKey": "log.02", "image": "assets/images/dossiers/dossier_elena_rostova.jpg", "authorKey": "author.elena_rostova", "classificationKey": "class.engineering" }, + { "id": "engineer_notes", "titleKey": "archive.engineer_notes.title", "contentKey": "archive.engineer_notes.content", "image": "assets/images/dossiers/dossier_elena_rostova.jpg", "authorKey": "author.elena_rostova", "classificationKey": "class.engineering" }, + { "id": "log_03", "titleKey": "archive.log_03.title", "contentKey": "log.03", "image": "assets/images/dossiers/dossier_dr_vance.jpg", "authorKey": "author.dr_vance", "classificationKey": "class.neural" }, + { "id": "core_manifest", "titleKey": "archive.core_manifest.title", "contentKey": "archive.core_manifest.content", "image": "assets/images/dossiers/dossier_dr_vance.jpg", "authorKey": "author.dr_vance", "classificationKey": "class.architect" } ], "rooms": [ { diff --git a/src/NullDepth/wwwroot/js/null-depth.js b/src/NullDepth/wwwroot/js/null-depth.js index b3d0b35..a09a2bc 100644 --- a/src/NullDepth/wwwroot/js/null-depth.js +++ b/src/NullDepth/wwwroot/js/null-depth.js @@ -133,8 +133,35 @@ oscillator.stop(context.currentTime + config[1] + 0.03); }, + heartbeatAudio: null, + + setHeartbeatBpm(bpm) { + if (bpm < 105 || this.volume === 0) { + if (this.heartbeatAudio) { + this.heartbeatAudio.pause(); + this.heartbeatAudio = null; + } + return; + } + if (!this.heartbeatAudio) { + this.heartbeatAudio = new Audio('assets/audio/sfx_heartbeat.wav'); + this.heartbeatAudio.loop = true; + this.heartbeatAudio.volume = Math.min(0.7, (bpm - 90) / 40 * 0.7) * this.volume; + this.heartbeatAudio.play().catch(() => {}); + } else { + const rate = Math.max(0.9, Math.min(2.0, bpm / 75.0)); + this.heartbeatAudio.playbackRate = rate; + this.heartbeatAudio.volume = Math.min(0.7, (bpm - 90) / 40 * 0.7) * this.volume; + if (this.heartbeatAudio.paused) this.heartbeatAudio.play().catch(() => {}); + } + }, + stopAll() { this.stopAmbient(); + if (this.heartbeatAudio) { + this.heartbeatAudio.pause(); + this.heartbeatAudio = null; + } this.context?.suspend(); } }; diff --git a/src/NullDepth/wwwroot/localization/en.json b/src/NullDepth/wwwroot/localization/en.json index 78a2fe5..3117c94 100644 --- a/src/NullDepth/wwwroot/localization/en.json +++ b/src/NullDepth/wwwroot/localization/en.json @@ -44,6 +44,16 @@ "panel.breaker.balanced": "BALANCED SECURITY", "panel.breaker.switch": "ENGAGE BREAKER", + "author.dr_vance": "Dr. Arthur Vance // Neural Research Director", + "author.elena_rostova": "Elena Rostova // Chief Cryogenic Engineer", + "author.marek_cale": "Commander Marek Cale // Chief Security Officer", + "class.sub03": "CLASSIFICATION: SUB-03 RESEARCH // CONFIDENTIAL", + "class.blackbox": "BLACK BOX // EMERGENCY PROTOCOL", + "class.security": "SECURITY JOURNAL // SECTOR LOG", + "class.engineering": "SERVICE DISPATCH // COOLING JUNCTION", + "class.neural": "NEURAL TELEMETRY // ARCHITECT PROJECT", + "class.architect": "SYSTEM MANIFEST // CORE SPECIFICATION", + "hotspot.loc_01.terminal": "CONTRACTOR TERMINAL", "hotspot.loc_01.gate": "PRESSURE GATE", "hotspot.loc_02.terminal": "PERSONNEL STATION", diff --git a/src/NullDepth/wwwroot/localization/sk.json b/src/NullDepth/wwwroot/localization/sk.json index 43168e4..b917a0a 100644 --- a/src/NullDepth/wwwroot/localization/sk.json +++ b/src/NullDepth/wwwroot/localization/sk.json @@ -44,6 +44,16 @@ "panel.breaker.balanced": "KOMBINOVANÝ REŽIM", "panel.breaker.switch": "ZOPNÚŤ ISTIČ", + "author.dr_vance": "Dr. Arthur Vance // Riaditeľ výskumu", + "author.elena_rostova": "Ing. Elena Rostová // Hlavná inžinierka kryochladenia", + "author.marek_cale": "Veliteľ Marek Cale // Šéf internej bezpečnosti", + "class.sub03": "KLASIFIKÁCIA: VÝSKUM SUB-03 // DÔVERNÉ", + "class.blackbox": "ČIERNA SKRINKA // NÚDZOVÝ PROTOKOL", + "class.security": "BEZPEČNOSTNÝ DENNÍK // SEKTOROVÝ ZÁZNAM", + "class.engineering": "SERVISNÁ SPRÁVA // UZOL CHLADENIA", + "class.neural": "NEURÁLNA TELEMETRIA // PROJEKT ARCHITECT", + "class.architect": "SYSTÉMOVÝ MANIFEST // ZÁZNAM JADRA", + "hotspot.loc_01.terminal": "KONTRAKTORSKÝ TERMINÁL", "hotspot.loc_01.gate": "TLAKOVÁ BRÁNA", "hotspot.loc_02.terminal": "PERSONÁLNA STANICA",