/* fx.jsx — sound engine, confetti, abstract thumbnails. Exported to window. */ /* ============ SOUND ============ */ let _ac = null; const sfx = { muted: false, ctx() { if (!_ac) { try { _ac = new (window.AudioContext || window.webkitAudioContext)(); } catch(e){} } if (_ac && _ac.state === "suspended") _ac.resume(); return _ac; }, tone(freq, t0, dur, type = "sine", gain = 0.18, slide = null) { const ac = this.ctx(); if (!ac) return; const o = ac.createOscillator(), g = ac.createGain(); o.type = type; o.frequency.setValueAtTime(freq, t0); if (slide) o.frequency.exponentialRampToValueAtTime(slide, t0 + dur); g.gain.setValueAtTime(0.0001, t0); g.gain.exponentialRampToValueAtTime(gain, t0 + 0.012); g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur); o.connect(g); g.connect(ac.destination); o.start(t0); o.stop(t0 + dur + 0.02); }, play(name) { if (this.muted) return; const ac = this.ctx(); if (!ac) return; const n = ac.currentTime; if (name === "complete") { [523.25, 659.25, 783.99, 1046.5].forEach((f, i) => this.tone(f, n + i*0.075, 0.28, "triangle", 0.16)); this.tone(1318.5, n + 0.30, 0.5, "sine", 0.08); } else if (name === "levelup") { [523.25, 698.46, 880, 1174.7, 1567.98].forEach((f,i)=>this.tone(f, n+i*0.09, 0.4, "sawtooth", 0.10)); this.tone(2093, n+0.5, 0.6, "sine", 0.06); } else if (name === "check") { this.tone(880, n, 0.09, "square", 0.07, 1320); } else if (name === "uncheck") { this.tone(440, n, 0.09, "square", 0.05, 300); } else if (name === "drop") { this.tone(160, n, 0.12, "sine", 0.14, 90); this.tone(320, n, 0.06, "triangle", 0.05); } else if (name === "pick") { this.tone(420, n, 0.05, "sine", 0.05, 560); } else if (name === "click") { this.tone(660, n, 0.04, "sine", 0.04); } else if (name === "move") { this.tone(523, n, 0.07, "triangle", 0.06, 660); } }, }; /* ============ CONFETTI ============ */ let _cv = null, _cx = null, _parts = [], _raf = null; function ensureCanvas() { if (_cv) return; _cv = document.createElement("canvas"); _cv.className = "confetti-canvas"; document.body.appendChild(_cv); _cx = _cv.getContext("2d"); const resize = () => { _cv.width = innerWidth * devicePixelRatio; _cv.height = innerHeight * devicePixelRatio; _cv.style.width = innerWidth+"px"; _cv.style.height = innerHeight+"px"; _cx.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0); }; resize(); addEventListener("resize", resize); } function tick() { _cx.clearRect(0, 0, innerWidth, innerHeight); _parts = _parts.filter(p => p.life > 0); for (const p of _parts) { p.vy += 0.16; p.x += p.vx; p.y += p.vy; p.vx *= 0.99; p.rot += p.vr; p.life -= 1; _cx.save(); _cx.translate(p.x, p.y); _cx.rotate(p.rot); _cx.globalAlpha = Math.min(1, p.life / 22); _cx.fillStyle = p.color; if (p.shape === 0) _cx.fillRect(-p.s/2, -p.s/2, p.s, p.s*0.5); else { _cx.beginPath(); _cx.arc(0,0,p.s/2,0,7); _cx.fill(); } _cx.restore(); } if (_parts.length) _raf = requestAnimationFrame(tick); else { _raf = null; _cx.clearRect(0,0,innerWidth,innerHeight); } } function fireConfetti(x, y, opts = {}) { ensureCanvas(); const accent = getComputedStyle(document.documentElement).getPropertyValue("--accent").trim() || "#ff7a00"; const colors = opts.colors || [accent, "#ff9633", "#f5b942", "#ffffff", "#ffd28a"]; const n = opts.count || 90; const spread = opts.spread || 1; for (let i = 0; i < n; i++) { const a = (Math.random() * Math.PI * 2); const sp = (4 + Math.random() * 9) * spread; _parts.push({ x, y, vx: Math.cos(a) * sp, vy: Math.sin(a) * sp - (opts.up ? 6 : 3), s: 5 + Math.random() * 7, color: colors[(Math.random()*colors.length)|0], rot: Math.random()*7, vr: (Math.random()-0.5)*0.4, life: 70 + Math.random()*45, shape: Math.random() < 0.6 ? 0 : 1, }); } if (!_raf) _raf = requestAnimationFrame(tick); } /* ============ ABSTRACT THUMBNAIL ============ */ /* themed, generated visuals — no stock photos. */ function Thumb({ kind, className }) { const styleFor = (k) => { const A = "var(--accent)", G = "var(--gold)", P = "var(--c-feature)"; const M = { garagem: `repeating-linear-gradient(115deg, #161616 0 22px, #1c1c1c 22px 44px), radial-gradient(60% 90% at 30% 110%, rgba(var(--accent-rgb),0.4), transparent 60%)`, inventario: `linear-gradient(0deg, rgba(0,0,0,0.4), transparent), repeating-conic-gradient(from 0deg at 50% 50%, #1a1a1a 0 25%, #161616 0 50%)`, veiculo: `radial-gradient(80% 120% at 70% 20%, rgba(var(--accent-rgb),0.45), transparent 55%), linear-gradient(160deg, #0e0e14, #1a1320)`, mdt: `linear-gradient(180deg, #10131c, #0a0d14), radial-gradient(50% 60% at 20% 30%, rgba(91,135,255,0.4), transparent)`, cnh: `linear-gradient(135deg, #1e1a12, #14130d), radial-gradient(60% 80% at 80% 20%, rgba(245,185,66,0.45), transparent)`, multas: `linear-gradient(135deg, #1a1212, #120d0d), radial-gradient(60% 80% at 20% 80%, rgba(240,69,58,0.4), transparent)`, }; return M[k] || `radial-gradient(70% 100% at 50% 0%, rgba(var(--accent-rgb),0.3), transparent), linear-gradient(160deg,#161616,#101010)`; }; return (