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
+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Falah Coach Portal</title> <title>Falah Coach Portal</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="8" fill="#0f172a"/><path d="M7 22 L16 8 L25 22" stroke="#10b981" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 243 B

+28 -46
View File
@@ -27,7 +27,7 @@ import {
========================================================================== */ ========================================================================== */
const GEMINI_API_KEY = 'AIzaSyDZn7tv1D3n2zuns8uaXIqp_z1FZTeVyKI'; 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. 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. 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); setSidebarOpen(false);
}; };
// ── Audio Player UI (reused in sidebar + mobile strip) ───────────────────
const progressPct = duration > 0 ? (currentTime / duration) * 100 : 0; 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 ──────────────────────────────────────────────────────────────── // ── Render ────────────────────────────────────────────────────────────────
return ( return (
@@ -705,7 +661,33 @@ export default function App() {
</nav> </nav>
<div className="mt-auto border-t border-slate-800 p-4 bg-slate-950/50 backdrop-blur-md"> <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>
</div> </div>