diff --git a/web/src/components/StatusBar/PetalsToggle.tsx b/web/src/components/StatusBar/PetalsToggle.tsx
new file mode 100644
index 0000000..83c72c1
--- /dev/null
+++ b/web/src/components/StatusBar/PetalsToggle.tsx
@@ -0,0 +1,37 @@
+import { useEffect, useState } from 'react'
+import { isPetalsEnabled, onPetalsEnabledChange, setPetalsEnabled } from '../../effects/petals'
+
+// A tiny toggle for Petal's ambient falling-blossom layer, sitting just left of
+// the sound toggle in the status bar. Some people find the drifting petals
+// distracting, so this turns them off entirely. Bilingual tooltip (she reads
+// Mandarin first), and the choice persists across reloads.
+export function PetalsToggle() {
+ const [on, setOn] = useState(isPetalsEnabled)
+
+ // Stay in sync if the setting is flipped elsewhere.
+ useEffect(() => onPetalsEnabledChange(setOn), [])
+
+ const toggle = () => {
+ const next = !on
+ setPetalsEnabled(next)
+ setOn(next)
+ }
+
+ return (
+
+ )
+}
diff --git a/web/src/components/StatusBar/StatusBar.tsx b/web/src/components/StatusBar/StatusBar.tsx
index c9b2f84..01aac4f 100644
--- a/web/src/components/StatusBar/StatusBar.tsx
+++ b/web/src/components/StatusBar/StatusBar.tsx
@@ -1,6 +1,7 @@
import { Fragment, useEffect, useRef, useState } from 'react'
import type { SaveStatus } from '../../hooks/useAutoSave'
import { StatsPanel } from './StatsPanel'
+import { PetalsToggle } from './PetalsToggle'
import { SoundToggle } from './SoundToggle'
interface Props {
@@ -145,7 +146,8 @@ export function StatusBar({ wordCount, text, saveStatus, checking, voicing, coll
>
)}
-
+
diff --git a/web/src/effects/PetalFall.tsx b/web/src/effects/PetalFall.tsx
index 8d01d92..8dea48a 100644
--- a/web/src/effects/PetalFall.tsx
+++ b/web/src/effects/PetalFall.tsx
@@ -1,4 +1,5 @@
-import { useEffect, useRef } from 'react'
+import { useEffect, useRef, useState } from 'react'
+import { isPetalsEnabled, onPetalsEnabledChange } from './petals'
// PetalFall is the cozy ambient layer: a fixed, non-interactive 2D canvas that
// drifts soft cherry-blossom petals down the page. Each petal is a real
@@ -212,8 +213,19 @@ interface Petal {
export function PetalFall({ night = false }: { night?: boolean }) {
const canvasRef = useRef
(null)
+ const [enabled, setEnabled] = useState(isPetalsEnabled)
+
+ // Let the status-bar toggle switch the ambient layer on/off live.
+ useEffect(() => onPetalsEnabledChange(setEnabled), [])
useEffect(() => {
+ if (!enabled) {
+ // User turned the ambient petals off — wipe any frame left on the canvas.
+ const el = canvasRef.current
+ el?.getContext('2d')?.clearRect(0, 0, el.width, el.height)
+ return
+ }
+
const reduce =
typeof window !== 'undefined' &&
window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
@@ -347,7 +359,7 @@ export function PetalFall({ night = false }: { night?: boolean }) {
document.removeEventListener('visibilitychange', onVisibility)
petals = []
}
- }, [night])
+ }, [night, enabled])
return (