The #frequencyBars container is a flexbox with align-items: flex-end: when a .bar's height grows, the bar rises upward — the growth axis is inverted relative to the DOM's natural reading direction. The JS generates .bar elements using the barCount constant (20 by default); flexbox distributes them automatically with a 4 px gap and centers them horizontally.
The loop starts on page load with an initial requestAnimationFrame(animate) call inside the IIFE. On each frame, getSimulatedAudio(barCount, time) computes 20 values in [0, 1]; animate then applies bar.style.height = (10 + data[i] × 100) + 'px' to each .bar. The CSS transition: height 0.05s visually smooths the jumps between frames — the browser handles interpolation with no extra per-frame JS computation.
getSimulatedAudio(count, time) simulates an audio spectrum without a microphone or audio file. For each bar i, an individual frequency is computed (freq = 1 + i × 0.5) and mixed across three components: sin(t × freq × 0.002) × 0.5 (slow wave), sin(t × freq × 0.003 + i) × 0.3 (bar-shifted wave), Math.random() × 0.2 (noise). The absolute value is clamped to [0, 1] and converted to a height: minimum 10 px (silence) · maximum 110 px (peak). For a real audio signal, replace the getSimulatedAudio() call with values from AnalyserNode.getByteFrequencyData() mapped to [0, 1].
The effect targets a single id="frequencyBars" — designed for one instance per page. For multiple simultaneous instances, pass the element as a parameter to the animation logic instead of calling getElementById directly inside the loop.
Without CSS Transitions, bars jump instantly to their target height with no interpolation — the effect is still legible, just choppy. Without Flexbox (IE 9 and older), the layout breaks; no fallback is provided in the code.