SecretoCho
menu

component 26 / 28WORKING

SC Hover Sound

type
Code Override
platform
Framer
license
SecretoCho Design
needs JavaScript

demo · runs with JavaScript

fig 00 · live demo, ported from UseHoverSound.tsx

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

  • override
  • hover
  • sound
  • web audio
  • oscillator
  • square wave
  • scale
  • ui feedback

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

  1. Add UseHoverSound.tsx to the project’s code files.
  2. Select a layer, open Code Overrides, pick the file and an override: useHoverSoundCard, useHoverSoundBox, useHoverSoundTitle, useHoverSoundImage, useHoverSoundSection or useHoverSoundButton.
  3. For lists apply useHoverSoundList00 … useHoverSoundList20 to the items in order; each step is the next note of the scale.
  4. 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

overridetypeenter → leave (Hz)for
useHoverSoundCardoverride540 → 300card layers
useHoverSoundBoxoverride620 → 400box layers
useHoverSoundTitleoverride720 → 440title layers
useHoverSoundImageoverride800 → 500image layers
useHoverSoundSectionoverride460 → 260section layers
useHoverSoundButtonoverride880 → 440button layers
useHoverSoundList00 … useHoverSoundList20overridescale[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 / 2list 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

Header comment

UseHoverSound.tsx has no doc comment.

props [REF]

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.

UseHoverSound.tsx · lines 57–88 · tsx
    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

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

read the CV · LinkedIn