9ae91dd3ed
UI layout (PlayCanvas + play/page.tsx): - "F · 全 · 屏" button (renamed from 演 · 示 to match what users actually mean by F) floats above the canvas, right-aligned, via a new `aboveCanvas` ReactNode slot that lives on the relative inline-block image wrapper at `bottom-full right-0`. It hugs the actual image right edge regardless of aspect ratio. - "有 · 声 / 静 · 音" button mirrors that on the left via a new `aboveCanvasLeft` slot. - Both slots also render inside the loading placeholder so the two controls appear from frame one, before the scene image arrives. - InfiPlot back-link grows from 15px to 22/26px (mobile/desktop) with a slightly larger arrow, matching the brand'\''s presence on the homepage hero. - Canvas-bottom metadata row (image dims on left, tutorial hint on right) dropped. The "—" placeholder and "···" loading state looked like stray punctuation; users found them noisy. - Footer collapses to a single centered "Ⅰ · Ⅰ" mark. Audio gating logic (play/page.tsx): - Collapse the two-flag audio gate into one source of truth. The homepage "语音配音" choice no longer lives in a separate `audioEnabledRef` flag that gates `fetchBeatAudio` independently of the in-page mute state. Instead the `muted` useState lazy initializer reads `sessionStorage["infiplot:custom"].audioEnabled` and projects it inversely (audioEnabled=false → muted=true) so the 静音/有声 button correctly reflects the homepage selection from the first frame. The in-page toggle remains the source of truth from then on (persisted to localStorage:infiplot:muted). - This fixes a visible disconnect where picking "关闭" on the homepage left the play page showing 有声 because the in-page state had no link to the homepage choice. - The sessionStorage read uses the renamed key "infiplot:custom" (the infiplot rename PR changed it from yume:custom on the home side but the play side hadn'\''t been updated to match). No new TTS quota is ever burned while muted: fetchBeatAudio'\''s mutedRef.current early-return is the only path to /api/beat-audio and is checked before the fetch fires; mute transitions also abort in-flight requests.