feat: soundboard, toast notifications, robust JSON parsing

Soundboard (Web Audio API):
- 8 ambient loops: rain, wind, fire, ocean, forest, tavern, thunder, cave
- 8 one-shot SFX: sword, bow, spell, door, coins, horn, dice, footsteps
- Brown noise generation for ambient textures
- Oscillator-based SFX with frequency sweeps
- Volume slider, active state indicators with pulsing dot
- Stop All button when ambients are playing
- Toast notifications on SFX trigger

Toast notification system:
- Zustand-based store with auto-dismiss (4s)
- 4 types: success, error, info, warning
- Slide-in animation from top-right
- Dismissible with × button
- Integrated into App shell

JSON parsing (all AI components):
- Shared lib/llm-parse.ts with extractJson, parseLlmJson, flattenValue, ensureArray
- Strips markdown code fences before parsing
- Handles nested objects where strings expected
- All 5 AI components use parseLlmJson instead of raw regex
- All prompts explicitly request raw JSON with no fences
- ItemForge shows raw response in <details> on parse failure

Full production build passing
This commit is contained in:
itsamejms
2026-06-28 23:08:15 +01:00
parent b23ab61841
commit a9cdd10806
7 changed files with 480 additions and 18 deletions
+6 -10
View File
@@ -26,6 +26,8 @@ import { CalendarWidget } from "./components/CalendarWidget";
import { WorldBuilder } from "./components/WorldBuilder";
import { ItemForge } from "./components/ItemForge";
import { QuestDesigner } from "./components/QuestDesigner";
import { Soundboard } from "./components/Soundboard";
import { ToastContainer } from "./components/Toast";
export type View =
| "dashboard"
@@ -77,16 +79,7 @@ function renderView(view: View): React.ReactNode {
case "items":
return <ItemForge />;
case "sound":
return (
<div className="flex items-center justify-center h-full text-[var(--color-text-dim)]">
<div className="text-center">
<Volume2 size={64} className="mx-auto mb-4 opacity-30" />
<p className="text-lg font-heading">Soundboard</p>
<p className="text-sm mt-2">Local audio clips for ambience</p>
<p className="text-xs mt-1 opacity-60">Coming soon</p>
</div>
</div>
);
return <Soundboard />;
default:
return null;
}
@@ -239,6 +232,9 @@ export default function App() {
) : null}
</main>
</div>
{/* Toast notifications */}
<ToastContainer />
</div>
);
}