Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions plugins/career/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,25 @@
Virtuoso plugin reports. These ride along in
<em>Settings → Export</em> automatically.</p>
</div>
<hr class="border-gray-800 my-3">
<div class="space-y-3 text-sm">
<label class="flex items-center justify-between gap-4">
<span>
<span class="text-gray-200 font-medium">Crowd sound reactions</span>
<span class="block text-xs text-gray-500">Cheers when the crowd's mood rises, boos when it drops. Uses each venue's own recordings.</span>
</span>
<input type="checkbox" id="career-sfx-toggle" class="accent-cyan-500 w-4 h-4">
</label>
</div>
<script>
(function () {
'use strict';
var KEY = 'feedBack-venue-crowd-sfx';
var box = document.getElementById('career-sfx-toggle');
if (!box) return;
try { box.checked = localStorage.getItem(KEY) === 'on'; } catch (e) { /* ok */ }
box.addEventListener('change', function () {
try { localStorage.setItem(KEY, box.checked ? 'on' : 'off'); } catch (e) { /* ok */ }
});
}());
</script>
Binary file modified plugins/career/venue-packs/bar/bored.mp4
Binary file not shown.
Binary file modified plugins/career/venue-packs/bar/cheer.mp4
Binary file not shown.
Binary file modified plugins/career/venue-packs/bar/clap.mp4
Binary file not shown.
Binary file modified plugins/career/venue-packs/bar/ecstatic.mp4
Binary file not shown.
Binary file modified plugins/career/venue-packs/bar/engaged.mp4
Binary file not shown.
4 changes: 4 additions & 0 deletions plugins/career/venue-packs/bar/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@
"intro": {
"video": "intro.mp4",
"audio": "bar-ambience.mp3"
},
"sfx": {
"up": "sfx-up.mp3",
"down": "sfx-down.mp3"
}
}
Binary file modified plugins/career/venue-packs/bar/neutral.mp4
Binary file not shown.
Binary file added plugins/career/venue-packs/bar/sfx-down.mp3
Binary file not shown.
Binary file added plugins/career/venue-packs/bar/sfx-up.mp3
Binary file not shown.
2 changes: 1 addition & 1 deletion static/tailwind.min.css

Large diffs are not rendered by default.

34 changes: 33 additions & 1 deletion static/v3/venue-crowd.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
const STREAK_MILESTONES = [25, 50, 100];
const CANPLAY_TIMEOUT_MS = 4000;
const DEV_FLAG_KEY = 'feedBack-venue-crowd-dev';
const SFX_KEY = 'feedBack-venue-crowd-sfx'; // 'on' | 'off' (default off)

// ---------------------------------------------------------------------
// Pure, clock-injected decision logic (unit-tested in
Expand Down Expand Up @@ -144,7 +145,11 @@
video: abs(m.intro && m.intro.video),
audio: abs(m.intro && m.intro.audio),
};
return { loops, stingers, intro };
const sfx = {
up: abs(m.sfx && m.sfx.up),
down: abs(m.sfx && m.sfx.down),
};
return { loops, stingers, intro, sfx };
}

function ensureVideos() {
Expand Down Expand Up @@ -410,6 +415,30 @@
return true;
}

let _sfxEl = null;

function sfxEnabled() {
try { return localStorage.getItem(SFX_KEY) === 'on'; } catch (_) { return false; }
}

// One-shot crowd reaction on committed mood transitions (toggleable):
// up the ladder → cheer, down → boos. Committed transitions are already
// hysteresis-limited, so this can't spam.
function playMoodSfx(direction) {
if (!sfxEnabled() || !_manifest || !_manifest.sfx || _introActive) return;
const url = direction > 0 ? _manifest.sfx.up : _manifest.sfx.down;
if (!url || typeof document === 'undefined') return;
if (!_sfxEl) {
_sfxEl = document.createElement('audio');
_sfxEl.preload = 'auto';
_sfxEl.style.display = 'none';
document.body.appendChild(_sfxEl);
}
_sfxEl.src = url;
_sfxEl.volume = 0.6;
_sfxEl.play().catch(() => { /* pre-gesture; skip silently */ });
}

function onSongPlay() {
// Song audio starting is the hard cue: the ambience must yield.
fadeAudioOut(1000);
Expand All @@ -430,8 +459,10 @@
if (sting && !_introActive && CROWD_RANK[machine.current] >= CROWD_RANK.neutral) {
playStinger(sting);
}
const prevRank = CROWD_RANK[machine.current];
const next = machine.update(d.state, now());
if (next) {
playMoodSfx(CROWD_RANK[next] - prevRank);
// A stinger or the intro owns the idle layer; defer the switch.
if (_stingerUntilEnded || _introActive) _pendingLoop = next;
else showLoop(next, FADE_MS);
Expand Down Expand Up @@ -489,6 +520,7 @@
_introGen++;
_introActive = false;
stopAudio();
if (_sfxEl && !_sfxEl.paused) _sfxEl.pause();
_stingerUntilEnded = false;
_pendingLoop = null;
_loadingLoop = null;
Expand Down
Loading