feat: implementar Modo Smart touch-first com comutador bidirecional sem logout

This commit is contained in:
2026-09-21 22:33:22 +00:00
parent 09e74f17f5
commit 1ce4455f37
14 changed files with 3716 additions and 4 deletions
+177
View File
@@ -0,0 +1,177 @@
// ============================================================================
// Utilitário de Feedback Sonoro (Web Audio API) e Tátil para o Modo Smart
// Funciona 100% offline, sem carregar arquivos de áudio externos pesados.
// ============================================================================
class SmartAudioManager {
private audioCtx: AudioContext | null = null;
private soundEnabled: boolean = true;
constructor() {
// Carregar preferência salva
try {
const saved = localStorage.getItem('tracksteel_smart_sound');
if (saved !== null) {
this.soundEnabled = saved === 'true';
}
} catch {
this.soundEnabled = true;
}
}
private getContext(): AudioContext | null {
try {
if (!this.audioCtx) {
const AudioContextClass = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
if (AudioContextClass) {
this.audioCtx = new AudioContextClass();
}
}
if (this.audioCtx && this.audioCtx.state === 'suspended') {
this.audioCtx.resume();
}
return this.audioCtx;
} catch (e) {
console.warn('Web Audio API não suportada neste dispositivo', e);
return null;
}
}
public isSoundEnabled(): boolean {
return this.soundEnabled;
}
public setSoundEnabled(enabled: boolean): void {
this.soundEnabled = enabled;
try {
localStorage.setItem('tracksteel_smart_sound', String(enabled));
} catch {
// ignore
}
}
public toggleSound(): boolean {
const newState = !this.soundEnabled;
this.setSoundEnabled(newState);
if (newState) {
this.playClick();
}
return newState;
}
/**
* Bip de Sucesso (Apontamento Concluído)
* Frequência harmônica dupla suave e ascendente (587Hz -> 880Hz, 120ms)
*/
public playSuccess(): void {
this.vibrate([70, 40, 70]);
if (!this.soundEnabled) return;
const ctx = this.getContext();
if (!ctx) return;
try {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
const now = ctx.currentTime;
// Inicia em Ré5 (587Hz) e sobe para Lá5 (880Hz)
osc.frequency.setValueAtTime(587.33, now);
osc.frequency.exponentialRampToValueAtTime(880, now + 0.08);
// Fade out suave para evitar estalo
gain.gain.setValueAtTime(0.18, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.13);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.14);
} catch (e) {
console.warn('Erro ao tocar som de sucesso:', e);
}
}
/**
* Bip de Alerta ou Erro (Saldo insuficiente, conflito)
* Frequência grave de aviso (220Hz, 180ms)
*/
public playAlert(): void {
this.vibrate([120, 80, 120]);
if (!this.soundEnabled) return;
const ctx = this.getContext();
if (!ctx) return;
try {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'triangle';
const now = ctx.currentTime;
osc.frequency.setValueAtTime(220, now);
osc.frequency.linearRampToValueAtTime(180, now + 0.18);
gain.gain.setValueAtTime(0.2, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.19);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.2);
} catch (e) {
console.warn('Erro ao tocar som de alerta:', e);
}
}
/**
* Clique sonoro rápido para botões touch
*/
public playClick(): void {
this.vibrate(30);
if (!this.soundEnabled) return;
const ctx = this.getContext();
if (!ctx) return;
try {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
const now = ctx.currentTime;
osc.frequency.setValueAtTime(520, now);
gain.gain.setValueAtTime(0.08, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.04);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.05);
} catch (e) {
// ignore
}
}
/**
* Vibração Háptica para smartphones e tablets
*/
public vibrate(pattern: number | number[]): void {
try {
if (typeof window !== 'undefined' && 'vibrate' in navigator) {
navigator.vibrate(pattern);
}
} catch {
// Navegadores sem suporte a vibração
}
}
}
export const smartAudio = new SmartAudioManager();