component 26 / 28WORKING
SC Hover Sound
- type
- Code Override
- platform
- Framer
- license
- SecretoCho Design
demo · runs with JavaScript
Hover-sound code overrides: square-wave enter/leave tones per element type and a 21-step C-major scale for lists, on one shared AudioContext.
UseHoverSound is the set of code overrides behind the old site’s hover sounds. Each override plays a short square-wave tone when the pointer enters a layer and a lower one when it leaves: six element types (card, box, title, image, section, button) with fixed pitches, and twenty-one list overrides (List00–List20) that climb a C-major scale so a list of items plays a melody as you sweep down it. One shared AudioContext, a global cooldown and a single “hovered” id keep nested layers from stacking tones.
- Overrides
- FX
- UI
Main
Overview
- Built With
- Framer code overrides (Override), React hooks, Web Audio API
- Layout Support
- None — overrides attach pointer handlers to any layer
- Orientation
- n/a
- Ideal Use Case
- Navigation, cards, service lists and buttons that should feel tactile
Tags
Code comment
UseHoverSound.tsx has no doc comment.
Dependencies
- Technologies
- Web Audio API (AudioContext, OscillatorNode, GainNode)
- Notes
- No audio files and no libraries; tones are synthesized.
Complexity
- Logic Level
- Low–Medium (shared module state, cooldown, hovered-id guard, delayed leave)
- Runtime Cost
- Very Low per tone; each mounted override adds two window listeners
- Component Size
- ~6 KB TSX (6,261 bytes, 216 lines)
- Notes
- Used on 2,779 layers of the old site (code/_usage.json), across 12 pages and 17 components.
Guide
Instructions
- Add UseHoverSound.tsx to the project’s code files.
- Select a layer, open Code Overrides, pick the file and an override: useHoverSoundCard, useHoverSoundBox, useHoverSoundTitle, useHoverSoundImage, useHoverSoundSection or useHoverSoundButton.
- For lists apply useHoverSoundList00 … useHoverSoundList20 to the items in order; each step is the next note of the scale.
- Sound starts after the visitor’s first click (or another user gesture such as a key press); pointer entry never resumes the AudioContext.
Examples / Presets
Code overrides have no property controls, so there are no presets; the demo exposes the overrides as actions (enable sound, mute) and plays the tones from the table under [REF].
Mechanics
How it Works
- Context: the first override to mount creates one AudioContext (webkitAudioContext fallback) shared by the module; window click and pointerenter listeners are meant to resume it when suspended (only the click does: pointerenter does not bubble).
- Tone: playTone makes a square-wave OscillatorNode at the frequency with a GainNode at 0.12 ramping exponentially to 0.001 over 0.15 s, then stops; a 120 ms global cooldown (SOUND_COOLDOWN) drops tones that come faster.
- Enter / leave: each override returns onPointerEnter and onPointerLeave only. A module-wide globalHoveredId makes sure one layer owns the hover; leaving schedules the leave tone 100 ms later. Entering a different layer within those 100 ms cancels it; re-entering the same layer does not (the id still matches, so the enter branch is skipped and the leave tone plays).
- Types: card 540 / 300 Hz, box 620 / 400, title 720 / 440, image 800 / 500, section 460 / 260, button 880 / 440 (enter / leave).
- Lists: useHoverSoundList(index) picks scale[index % 21] from a 21-note C-major scale, C4 261.63 Hz to B6 1975.53 Hz, and plays half that frequency on leave.
Performance & Tuning
- Each override instance adds two window listeners on mount (removed on unmount); with thousands of layers that is thousands of listeners — apply overrides to the layers that matter.
- The cooldown caps the tone rate at about eight per second; oscillators are created per tone and stop themselves.
Compatibility & Limits
- Evergreen browsers with Web Audio; every engine starts the context suspended until a user gesture; only the click listener helps.
- Pointer events only: on touch devices pointerenter fires with the tap.
- The resume listeners are click and pointerenter on window; pointerenter does not bubble, so the window listener fires only when the pointer enters the page itself — in practice the first click starts the sound.
- No volume control, mute or reduced-motion / sound preference in the file.
Quality
Accessibility
- Unexpected audio can disturb visitors: offer a visible mute and respect a stored preference in a fork.
- Tones are short (150 ms) and quiet (gain 0.12), and never carry information the layer does not also show.
Security & Privacy
- No network calls, no audio files, no storage, no permissions (Web Audio output needs none; no microphone).
- Module-level state (AudioContext, hovered id, cooldown) is shared by every override on the page.
- Two window listeners (click, pointerenter) per mounted override; a console.warn if resuming the context fails.
Reference
Overrides
| override | type | enter → leave (Hz) | for |
|---|---|---|---|
useHoverSoundCard | override | 540 → 300 | card layers |
useHoverSoundBox | override | 620 → 400 | box layers |
useHoverSoundTitle | override | 720 → 440 | title layers |
useHoverSoundImage | override | 800 → 500 | image layers |
useHoverSoundSection | override | 460 → 260 | section layers |
useHoverSoundButton | override | 880 → 440 | button layers |
useHoverSoundList00 … useHoverSoundList20 | override | scale[index]: 261.63, 293.66, 329.63, 349.23, 392.0, 440.0, 493.88, 523.25, 587.33, 659.25, 698.46, 783.99, 880.0, 987.77, 1046.5, 1174.66, 1318.51, 1396.91, 1567.98, 1760.0, 1975.53 → enter / 2 | list items, by index |
Code overrides take no property controls; the table lists the exported overrides with their enter → leave tones.
Notes & Tips
- Keep the list indices in visual order so a sweep plays an ascending scale.
- Use useHoverSoundSection on large areas and the higher button tone on calls to action.
- Nested layers: each layer plays its enter tone as the pointer reaches it (the 120 ms cooldown permitting); the hovered-id guard only stops the outer layer’s leave tone once an inner layer has taken over.
Troubleshooting / FAQ
No sound at first
The AudioContext is suspended until the visitor’s first click (or another user gesture); pointer entry never resumes it.
Fast sweeps skip notes
Expected: the 120 ms cooldown drops tones that arrive too quickly.
Two tones at once on nested layers
Not expected: entering another override layer cancels the pending leave tone, and the shared 120 ms cooldown stops a second tone from starting; at most the last 30 ms of a 150 ms tone overlaps the next.
A leave tone plays although the pointer came straight back
Expected: re-entering the same layer within 100 ms does not clear the pending leave; only entering another override layer does.
Source
Key excerpt
Every hover sound is one square-wave oscillator: a fixed frequency, a 0.12 gain that decays exponentially over 150 ms, and a 120 ms cooldown so a fast sweep does not stutter.
const playTone = (freq: number, duration = 0.15) => { if (!audioCtx) return if (audioCtx.state === "suspended") { audioCtx .resume() .catch((err) => console.warn("Failed to resume AudioContext:", err) ) } const now = performance.now() if (now - lastSoundTime < SOUND_COOLDOWN) return lastSoundTime = now const osc = audioCtx.createOscillator() const gain = audioCtx.createGain() osc.type = "square" osc.frequency.setValueAtTime(freq, audioCtx.currentTime) gain.gain.setValueAtTime(0.12, audioCtx.currentTime) gain.gain.exponentialRampToValueAtTime( 0.001, audioCtx.currentTime + duration ) osc.connect(gain) gain.connect(audioCtx.destination) osc.start() osc.stop(audioCtx.currentTime + duration) }Credits
Credits
© SecretoCho Design
- license
- SecretoCho Design
SecretoCho Design
Disclaimer
Provided “as is” for visual/UX purposes. Validate performance, accessibility, and licensing within your project.
Proves
Hiring a design engineer?
I’m open to senior design engineer roles: remote from Belgrade, or relocation with visa sponsorship. Write to me, or read the CV first. Client work stays selective, one project at a time.
sc@secretocho.design