archy/neode-ui/src/composables/useNavSounds.ts
Dorian 486fc39249 feat: complete Phase 1 foundation hardening + three-mode UI design doc
Phase 1a — Gradient Removal:
- Replaced all gradient-button/gradient-card with glass-button/path-option-card
- Removed banned gradient CSS classes

Phase 1b — Security Hardening:
- SecretsManager: AES-256-GCM encryption (core/security)
- electrs_status: credentials from env vars instead of hardcoded
- port_manager: RwLock proper error handling (no unwrap)
- Pinned all 11 :latest manifest images to specific versions
- parmanode converter: pinned inferred image versions

Phase 1c — Code Quality:
- Split rpc.rs (1795 lines) into 6 handler modules (auth, node, container, package, peers)
- Removed sideload code (UI, store, RPC client, 3 doc files)
- Fixed body background flash on logout/refresh
- Replaced 30 TypeScript `any` types with proper types
- Deleted HelloWorld.vue, removed TODO comments
- Added set -euo pipefail to all shell scripts
- Made deploy script verbose with timestamps and elapsed time

Also adds:
- CLAUDE.md project guide
- docs/three-mode-ui-design.md — design spec for Easy/Pro/Chat UI modes
- OnlineStatusPill component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 05:23:42 +00:00

71 lines
1.8 KiB
TypeScript

/**
* Epic interface sounds for controller/keyboard navigation.
* Layered synthesis - cool, impactful, celebratory for actions.
*/
let audioContext: AudioContext | null = null
function getContext(): AudioContext | null {
if (audioContext) return audioContext
try {
audioContext = new (window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext)()
return audioContext
} catch {
return null
}
}
function playTone(
ctx: AudioContext,
freq: number,
duration: number,
gain: number,
type: OscillatorType = 'sine',
startOffset = 0
) {
const osc = ctx.createOscillator()
const g = ctx.createGain()
osc.connect(g)
g.connect(ctx.destination)
g.gain.setValueAtTime(0, ctx.currentTime)
g.gain.linearRampToValueAtTime(gain, ctx.currentTime + 0.01)
g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration)
osc.frequency.value = freq
osc.type = type
osc.start(ctx.currentTime + startOffset)
osc.stop(ctx.currentTime + startOffset + duration)
}
export function playNavSound(type: 'move' | 'select' | 'action' | 'back' = 'move') {
if (type === 'move') {
const audio = new Audio('/assets/audio/arrows.mp3')
audio.volume = 0.5
audio.play().catch(() => {})
return
}
if (type === 'select' || type === 'action') {
const audio = new Audio('/assets/audio/enter.mp3')
audio.volume = 0.5
audio.play().catch(() => {})
return
}
const ctx = getContext()
if (!ctx) return
try {
if (ctx.state === 'suspended') ctx.resume()
} catch {
return
}
switch (type) {
case 'back': {
playTone(ctx, 440, 0.06, 0.08, 'sine')
playTone(ctx, 330, 0.08, 0.05, 'sine', 0.03)
playTone(ctx, 220, 0.1, 0.04, 'triangle', 0.05)
break
}
}
}