improvements
@@ -16,6 +16,9 @@ public sealed class AudioService(IJSRuntime js)
|
|||||||
public async ValueTask PlaySfxAsync(string? source) =>
|
public async ValueTask PlaySfxAsync(string? source) =>
|
||||||
await js.InvokeVoidAsync("nullDepth.audio.playSfx", 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() =>
|
public async ValueTask StopAsync() =>
|
||||||
await js.InvokeVoidAsync("nullDepth.audio.stopAll");
|
await js.InvokeVoidAsync("nullDepth.audio.stopAll");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public sealed class ItemDefinition
|
|||||||
public string DescriptionKey { get; set; } = "";
|
public string DescriptionKey { get; set; } = "";
|
||||||
public string? DetailedKey { get; set; }
|
public string? DetailedKey { get; set; }
|
||||||
public string Category { get; set; } = "equipment";
|
public string Category { get; set; } = "equipment";
|
||||||
|
public string? Image { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ArchiveDefinition
|
public sealed class ArchiveDefinition
|
||||||
@@ -24,6 +25,9 @@ public sealed class ArchiveDefinition
|
|||||||
public string Id { get; set; } = "";
|
public string Id { get; set; } = "";
|
||||||
public string TitleKey { get; set; } = "";
|
public string TitleKey { get; set; } = "";
|
||||||
public string ContentKey { 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
|
public sealed class RoomDefinition
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
else if (_screen == Screen.Briefing)
|
else if (_screen == Screen.Briefing)
|
||||||
{
|
{
|
||||||
<section class="briefing-screen screen-image" style="background-image:url('assets/images/title/title_null_depth.webp')">
|
<section class="briefing-screen screen-image" style="background-image:url('assets/images/briefing/briefing_arrival.jpg')">
|
||||||
<div class="vignette"></div>
|
<div class="vignette"></div>
|
||||||
<article class="terminal-card briefing-card">
|
<article class="terminal-card briefing-card">
|
||||||
<p class="eyebrow">@Game.T("briefing.header")</p>
|
<p class="eyebrow">@Game.T("briefing.header")</p>
|
||||||
@@ -298,14 +298,25 @@
|
|||||||
if (selectedItem is not null)
|
if (selectedItem is not null)
|
||||||
{
|
{
|
||||||
<div class="item-inspect-box">
|
<div class="item-inspect-box">
|
||||||
<strong>@Game.T("inventory.inspectPrompt")</strong>
|
<div class="item-inspect-layout">
|
||||||
<p style="margin:.4rem 0 0;">@(selectedItem.DetailedKey is not null ? Game.T(selectedItem.DetailedKey) : Game.T(selectedItem.DescriptionKey))</p>
|
@if (!string.IsNullOrWhiteSpace(selectedItem.Image))
|
||||||
@if (selectedItem.Id == "diagnostic_scanner")
|
{
|
||||||
{
|
<div class="item-inspect-image-wrap">
|
||||||
<div class="inspect-actions">
|
<img src="@selectedItem.Image" alt="@Game.T(selectedItem.NameKey)" class="item-inspect-image" />
|
||||||
<button class="primary-action" @onclick="TriggerScanFromInventory"><kbd>C</kbd> @Game.T("common.scan")</button>
|
</div>
|
||||||
|
}
|
||||||
|
<div class="item-inspect-info">
|
||||||
|
<span class="item-category-badge">@selectedItem.Category.ToUpperInvariant() // SPECIFICATION</span>
|
||||||
|
<strong>@Game.T(selectedItem.NameKey)</strong>
|
||||||
|
<p style="margin:.3rem 0 0;">@(selectedItem.DetailedKey is not null ? Game.T(selectedItem.DetailedKey) : Game.T(selectedItem.DescriptionKey))</p>
|
||||||
|
@if (selectedItem.Id == "diagnostic_scanner")
|
||||||
|
{
|
||||||
|
<div class="inspect-actions">
|
||||||
|
<button class="primary-action" @onclick="TriggerScanFromInventory"><kbd>C</kbd> @Game.T("common.scan")</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,7 +345,25 @@
|
|||||||
{
|
{
|
||||||
<details>
|
<details>
|
||||||
<summary>@Game.T(entry.TitleKey)</summary>
|
<summary>@Game.T(entry.TitleKey)</summary>
|
||||||
<p class="terminal-copy">@Game.T(entry.ContentKey)</p>
|
<div class="archive-body">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(entry.Image))
|
||||||
|
{
|
||||||
|
<div class="archive-dossier-wrap">
|
||||||
|
<img src="@entry.Image" alt="Personnel Dossier" class="archive-dossier-portrait" />
|
||||||
|
<div class="archive-dossier-meta">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(entry.ClassificationKey))
|
||||||
|
{
|
||||||
|
<span class="dossier-badge">@Game.T(entry.ClassificationKey)</span>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(entry.AuthorKey))
|
||||||
|
{
|
||||||
|
<strong class="dossier-author">@Game.T(entry.AuthorKey)</strong>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<p class="terminal-copy">@Game.T(entry.ContentKey)</p>
|
||||||
|
</div>
|
||||||
</details>
|
</details>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -500,6 +529,7 @@
|
|||||||
await Game.NewGameAsync();
|
await Game.NewGameAsync();
|
||||||
_screen = Screen.Briefing;
|
_screen = Screen.Briefing;
|
||||||
_notice = null;
|
_notice = null;
|
||||||
|
await Audio.PlayAmbientAsync("assets/audio/amb_facility.mp3");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ContinueGame()
|
private async Task ContinueGame()
|
||||||
@@ -511,12 +541,14 @@
|
|||||||
_ending = Game.State.CompletedEnding;
|
_ending = Game.State.CompletedEnding;
|
||||||
_screen = _ending is null ? Screen.Game : Screen.Ending;
|
_screen = _ending is null ? Screen.Game : Screen.Ending;
|
||||||
await Audio.PlayAmbientAsync(Game.CurrentRoom.Ambient);
|
await Audio.PlayAmbientAsync(Game.CurrentRoom.Ambient);
|
||||||
|
await Audio.SetHeartbeatBpmAsync(Game.CurrentBpm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task BeginPlay()
|
private async Task BeginPlay()
|
||||||
{
|
{
|
||||||
_screen = Screen.Game;
|
_screen = Screen.Game;
|
||||||
await Audio.PlayAmbientAsync(Game.CurrentRoom.Ambient);
|
await Audio.PlayAmbientAsync(Game.CurrentRoom.Ambient);
|
||||||
|
await Audio.SetHeartbeatBpmAsync(Game.CurrentBpm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Move(string direction)
|
private async Task Move(string direction)
|
||||||
@@ -530,6 +562,7 @@
|
|||||||
var result = await Game.MoveAsync(direction);
|
var result = await Game.MoveAsync(direction);
|
||||||
if (result is not null) _notice = result;
|
if (result is not null) _notice = result;
|
||||||
if (result == "") { _notice = null; await Audio.PlayAmbientAsync(Game.CurrentRoom.Ambient); }
|
if (result == "") { _notice = null; await Audio.PlayAmbientAsync(Game.CurrentRoom.Ambient); }
|
||||||
|
await Audio.SetHeartbeatBpmAsync(Game.CurrentBpm);
|
||||||
await Task.Delay(260);
|
await Task.Delay(260);
|
||||||
_isTransitioning = false;
|
_isTransitioning = false;
|
||||||
}
|
}
|
||||||
@@ -561,6 +594,10 @@
|
|||||||
_choiceIndex = 0;
|
_choiceIndex = 0;
|
||||||
_selectedBreaker = 0;
|
_selectedBreaker = 0;
|
||||||
_notice = null;
|
_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);
|
private void SelectInteraction(InteractionDefinition interaction) => OpenInteraction(interaction);
|
||||||
@@ -578,7 +615,7 @@
|
|||||||
}
|
}
|
||||||
if (hs.TelemetryKey is not null)
|
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);
|
_notice = Game.T(hs.TelemetryKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,7 +623,7 @@
|
|||||||
private async Task ToggleScanMode()
|
private async Task ToggleScanMode()
|
||||||
{
|
{
|
||||||
_scanMode = !_scanMode;
|
_scanMode = !_scanMode;
|
||||||
await Audio.PlaySfxAsync("assets/audio/sfx_ui_confirm.mp3");
|
await Audio.PlaySfxAsync("assets/audio/sfx_lidar_scan.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ResolveInteraction()
|
private async Task ResolveInteraction()
|
||||||
@@ -598,6 +635,7 @@
|
|||||||
_interaction = null;
|
_interaction = null;
|
||||||
if (resolved.SetFlags.Contains("power_restored") || resolved.Sound?.Contains("alarm") == true || resolved.Sound?.Contains("disturbance") == true)
|
if (resolved.SetFlags.Contains("power_restored") || resolved.Sound?.Contains("alarm") == true || resolved.Sound?.Contains("disturbance") == true)
|
||||||
_ = TriggerSystemFlashAsync();
|
_ = TriggerSystemFlashAsync();
|
||||||
|
await Audio.SetHeartbeatBpmAsync(Game.CurrentBpm);
|
||||||
await Task.Delay(180);
|
await Task.Delay(180);
|
||||||
_notice = Game.T(resolved.ContentKey);
|
_notice = Game.T(resolved.ContentKey);
|
||||||
}
|
}
|
||||||
@@ -605,19 +643,27 @@
|
|||||||
private async Task ResolveChoice(ChoiceDefinition choice)
|
private async Task ResolveChoice(ChoiceDefinition choice)
|
||||||
{
|
{
|
||||||
await Game.ApplyChoiceAsync(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;
|
_interaction = null;
|
||||||
if (choice.Sound?.Contains("alarm") == true || choice.Sound?.Contains("disturbance") == true)
|
if (choice.Sound?.Contains("alarm") == true || choice.Sound?.Contains("disturbance") == true)
|
||||||
_ = TriggerSystemFlashAsync();
|
_ = TriggerSystemFlashAsync();
|
||||||
if (choice.Death) { _screen = Screen.Death; return; }
|
if (choice.Death) { await Audio.SetHeartbeatBpmAsync(0); _screen = Screen.Death; return; }
|
||||||
if (!string.IsNullOrWhiteSpace(choice.Ending)) { _ending = choice.Ending; _screen = Screen.Ending; 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);
|
await Task.Delay(180);
|
||||||
_notice = choice.ResultKey is null ? null : Game.T(choice.ResultKey);
|
_notice = choice.ResultKey is null ? null : Game.T(choice.ResultKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task TriggerScan()
|
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();
|
_notice = Game.ScanCurrentRoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,6 +696,7 @@
|
|||||||
_screen = Screen.Title;
|
_screen = Screen.Title;
|
||||||
_menuIndex = 0;
|
_menuIndex = 0;
|
||||||
_titleNotice = null;
|
_titleNotice = null;
|
||||||
|
await Audio.SetHeartbeatBpmAsync(0);
|
||||||
await Audio.PlayAmbientAsync("assets/audio/music_title_vocal.mp3");
|
await Audio.PlayAmbientAsync("assets/audio/music_title_vocal.mp3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 822 KiB |
|
After Width: | Height: | Size: 778 KiB |
|
After Width: | Height: | Size: 836 KiB |
|
After Width: | Height: | Size: 843 KiB |
|
After Width: | Height: | Size: 892 KiB |
|
After Width: | Height: | Size: 841 KiB |
|
After Width: | Height: | Size: 748 KiB |
|
After Width: | Height: | Size: 842 KiB |
@@ -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.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; }
|
.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 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 { 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: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 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 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; }
|
.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); }
|
.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); }
|
.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; }
|
.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; }
|
||||||
|
|||||||
@@ -8,38 +8,42 @@
|
|||||||
"nameKey": "item.contractor_credential.name",
|
"nameKey": "item.contractor_credential.name",
|
||||||
"descriptionKey": "item.contractor_credential.description",
|
"descriptionKey": "item.contractor_credential.description",
|
||||||
"detailedKey": "item.contractor_credential.detail",
|
"detailedKey": "item.contractor_credential.detail",
|
||||||
"category": "credential"
|
"category": "credential",
|
||||||
|
"image": "assets/images/items/item_contractor_credential.jpg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "diagnostic_scanner",
|
"id": "diagnostic_scanner",
|
||||||
"nameKey": "item.diagnostic_scanner.name",
|
"nameKey": "item.diagnostic_scanner.name",
|
||||||
"descriptionKey": "item.diagnostic_scanner.description",
|
"descriptionKey": "item.diagnostic_scanner.description",
|
||||||
"detailedKey": "item.diagnostic_scanner.detail",
|
"detailedKey": "item.diagnostic_scanner.detail",
|
||||||
"category": "tool"
|
"category": "tool",
|
||||||
|
"image": "assets/images/items/item_diagnostic_scanner.jpg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "thermal_clamp",
|
"id": "thermal_clamp",
|
||||||
"nameKey": "item.thermal_clamp.name",
|
"nameKey": "item.thermal_clamp.name",
|
||||||
"descriptionKey": "item.thermal_clamp.description",
|
"descriptionKey": "item.thermal_clamp.description",
|
||||||
"detailedKey": "item.thermal_clamp.detail",
|
"detailedKey": "item.thermal_clamp.detail",
|
||||||
"category": "tool"
|
"category": "tool",
|
||||||
|
"image": "assets/images/items/item_thermal_clamp.jpg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "manual_override",
|
"id": "manual_override",
|
||||||
"nameKey": "item.manual_override.name",
|
"nameKey": "item.manual_override.name",
|
||||||
"descriptionKey": "item.manual_override.description",
|
"descriptionKey": "item.manual_override.description",
|
||||||
"detailedKey": "item.manual_override.detail",
|
"detailedKey": "item.manual_override.detail",
|
||||||
"category": "module"
|
"category": "module",
|
||||||
|
"image": "assets/images/items/item_manual_override.jpg"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"archive": [
|
"archive": [
|
||||||
{ "id": "log_01", "titleKey": "archive.log_01.title", "contentKey": "log.01" },
|
{ "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" },
|
{ "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" },
|
{ "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" },
|
{ "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" },
|
{ "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" },
|
{ "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" }
|
{ "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": [
|
"rooms": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -133,8 +133,35 @@
|
|||||||
oscillator.stop(context.currentTime + config[1] + 0.03);
|
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() {
|
stopAll() {
|
||||||
this.stopAmbient();
|
this.stopAmbient();
|
||||||
|
if (this.heartbeatAudio) {
|
||||||
|
this.heartbeatAudio.pause();
|
||||||
|
this.heartbeatAudio = null;
|
||||||
|
}
|
||||||
this.context?.suspend();
|
this.context?.suspend();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,6 +44,16 @@
|
|||||||
"panel.breaker.balanced": "BALANCED SECURITY",
|
"panel.breaker.balanced": "BALANCED SECURITY",
|
||||||
"panel.breaker.switch": "ENGAGE BREAKER",
|
"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.terminal": "CONTRACTOR TERMINAL",
|
||||||
"hotspot.loc_01.gate": "PRESSURE GATE",
|
"hotspot.loc_01.gate": "PRESSURE GATE",
|
||||||
"hotspot.loc_02.terminal": "PERSONNEL STATION",
|
"hotspot.loc_02.terminal": "PERSONNEL STATION",
|
||||||
|
|||||||
@@ -44,6 +44,16 @@
|
|||||||
"panel.breaker.balanced": "KOMBINOVANÝ REŽIM",
|
"panel.breaker.balanced": "KOMBINOVANÝ REŽIM",
|
||||||
"panel.breaker.switch": "ZOPNÚŤ ISTIČ",
|
"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.terminal": "KONTRAKTORSKÝ TERMINÁL",
|
||||||
"hotspot.loc_01.gate": "TLAKOVÁ BRÁNA",
|
"hotspot.loc_01.gate": "TLAKOVÁ BRÁNA",
|
||||||
"hotspot.loc_02.terminal": "PERSONÁLNA STANICA",
|
"hotspot.loc_02.terminal": "PERSONÁLNA STANICA",
|
||||||
|
|||||||