fix: Gemini model -> gemini-2.5-flash, stable DOM player, favicon
Deploy to Netlify / build-and-deploy (push) Successful in 1m2s

- Update Gemini API model from deprecated gemini-2.0-flash to gemini-2.5-flash
- Remove SidebarPlayer sub-component from inside App() - caused DOM remounts
  every 250ms (currentTime tick) making Pause/Skip buttons unclickable
- Inline player JSX directly in render tree for stable DOM nodes
- Add favicon.svg (dark bg + emerald trend line) to fix 404 console errors
This commit is contained in:
wmj
2026-08-24 11:21:55 +08:00
parent 2a24a56f50
commit f8aacf3a69
3 changed files with 30 additions and 47 deletions
+28 -46
View File
@@ -27,7 +27,7 @@ import {
========================================================================== */
const GEMINI_API_KEY = 'AIzaSyDZn7tv1D3n2zuns8uaXIqp_z1FZTeVyKI';
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}`;
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}`;
const AI_SYSTEM_PROMPT = `You are a hostile, senior executive playing a roleplay simulation for management consultants at Falah Consulting.
Your persona: a hard-nosed C-suite executive (CIO, CFO, or CEO) who is deeply sceptical of consultants and demands hard financial evidence.
@@ -606,52 +606,8 @@ export default function App() {
setSidebarOpen(false);
};
// ── Audio Player UI (reused in sidebar + mobile strip) ───────────────────
const progressPct = duration > 0 ? (currentTime / duration) * 100 : 0;
const SidebarPlayer = () => (
activeTrack ? (
<div className="animate-fade-in-up">
<div className="flex justify-between items-center mb-2">
<span className="text-[10px] font-bold text-emerald-400 uppercase tracking-wider flex items-center gap-1">
<Volume2 size={10} /> Now Playing
</span>
<span className="text-xs text-slate-500 font-mono">{formatTime(currentTime)} / {activeTrack.duration}</span>
</div>
<p className="font-semibold text-sm text-white truncate mb-0.5" title={activeTrack.title}>{activeTrack.title}</p>
<p className="text-xs text-slate-400 mb-3 truncate">{activeTrack.author}</p>
{/* Seekable progress bar */}
<div
className="w-full bg-slate-800 h-1.5 rounded-full overflow-hidden cursor-pointer mb-3"
onClick={seek}
title="Click to seek"
>
<div
className="bg-emerald-500 h-full rounded-full transition-all duration-300"
style={{ width: `${progressPct}%` }}
/>
</div>
<div className="flex items-center justify-between px-2">
<button aria-label="Previous track" onClick={skipPrev} className="text-slate-400 hover:text-white transition-colors"><SkipBack size={18} /></button>
<button
onClick={togglePlay}
aria-label={isPlaying ? 'Pause' : 'Play'}
className="text-white bg-emerald-500 hover:bg-emerald-400 rounded-full w-10 h-10 flex items-center justify-center transition-all shadow-lg shadow-emerald-500/20"
>
{isPlaying ? <PauseCircle size={24} className="text-slate-950" /> : <PlayCircle size={24} className="text-slate-950" />}
</button>
<button aria-label="Next track" onClick={skipNext} className="text-slate-400 hover:text-white transition-colors"><SkipForward size={18} /></button>
</div>
</div>
) : (
<div className="py-4 text-center">
<Headphones className="mx-auto text-slate-700 mb-2" size={24} />
<p className="text-xs text-slate-500 font-medium">Select an audio brief to begin</p>
</div>
)
);
// ── Render ────────────────────────────────────────────────────────────────
return (
@@ -705,7 +661,33 @@ export default function App() {
</nav>
<div className="mt-auto border-t border-slate-800 p-4 bg-slate-950/50 backdrop-blur-md">
<SidebarPlayer />
{activeTrack ? (
<div>
<div className="flex justify-between items-center mb-2">
<span className="text-[10px] font-bold text-emerald-400 uppercase tracking-wider flex items-center gap-1">
<Volume2 size={10} /> Now Playing
</span>
<span className="text-xs text-slate-500 font-mono">{formatTime(currentTime)} / {activeTrack.duration}</span>
</div>
<p className="font-semibold text-sm text-white truncate mb-0.5" title={activeTrack.title}>{activeTrack.title}</p>
<p className="text-xs text-slate-400 mb-3 truncate">{activeTrack.author}</p>
<div className="w-full bg-slate-800 h-1.5 rounded-full overflow-hidden cursor-pointer mb-3" onClick={seek} title="Click to seek">
<div className="bg-emerald-500 h-full rounded-full transition-all duration-300" style={{ width: `${progressPct}%` }} />
</div>
<div className="flex items-center justify-between px-2">
<button aria-label="Previous track" onClick={skipPrev} className="text-slate-400 hover:text-white transition-colors"><SkipBack size={18} /></button>
<button onClick={togglePlay} aria-label={isPlaying ? 'Pause' : 'Play'} className="text-white bg-emerald-500 hover:bg-emerald-400 rounded-full w-10 h-10 flex items-center justify-center transition-all shadow-lg shadow-emerald-500/20">
{isPlaying ? <PauseCircle size={24} className="text-slate-950" /> : <PlayCircle size={24} className="text-slate-950" />}
</button>
<button aria-label="Next track" onClick={skipNext} className="text-slate-400 hover:text-white transition-colors"><SkipForward size={18} /></button>
</div>
</div>
) : (
<div className="py-4 text-center">
<Headphones className="mx-auto text-slate-700 mb-2" size={24} />
<p className="text-xs text-slate-500 font-medium">Select an audio brief to begin</p>
</div>
)}
</div>
</div>