Strobe Lighting Effect

Light beams emanate from the corners, sweep across, and brighten where they cross - full-page or container-scoped, with named presets.

Domma.effects.strobe()

Renders a canvas of light beams that emanate from the corners of the page - or a container - and sweep or rotate across the space. Where beams overlap, their light adds together and brightens, just like real stage lighting. Pass null as the selector for a full-page lighting rig, or a selector to scope it to a single element. Everything is driven by a named preset, and any individual option overrides the preset's value. The effect returns a control object so you can pause, resume, stop, restart or destroy it at any time. Under prefers-reduced-motion the lighting is disabled entirely.

// Full-page club lighting
const lights = Domma.effects.strobe(null);
// A named preset
Domma.effects.strobe('#stage', { preset: 'police' });
// Preset + overrides
Domma.effects.strobe(null, { preset: 'club', flicker: false, colours: ['#ffffff'] });
lights.pause(); lights.resume(); lights.stop(); lights.destroy();

Options

Option Type Default Description
preset string 'club' Bundle of the options below. One of club, concert, police, searchlight, scanner, mood. Individual options override the preset.
origins string[] preset Which corners beams emit from - any of 'tl', 'tr', 'bl', 'br'.
motion string preset 'sweep' (fan back and forth) or 'rotate' (full circle).
sweepArc number preset Arc, in degrees, that beams fan through in sweep mode.
speed number preset Sweep / rotation speed multiplier.
beamWidth number preset Beam cone width in degrees.
flicker boolean preset Blink the beams on and off.
hz number preset Flicker rate (flashes per second). Values above 5 Hz log a console warning but are never clamped - keep it low for safety.
colours string[] preset Hex colour strings, assigned one per beam.
intensity number preset Overall brightness, 0-1.
duration number | null null Auto-stop after this many milliseconds. null runs until stopped manually.
zIndex number 9999 Canvas stacking order.
respectMotionPreference boolean true Honour the prefers-reduced-motion OS setting. When true and reduced motion is set, the lighting does not run.

Presets

Each preset bundles a complete lighting look. Start from one, then override any option you like.

Presets
club
Four-corner crisscross, multi-colour, fast flicker. Domma.effects.strobe(null, { preset: 'club' })
concert
Two top beams fanning wide, rainbow, medium flicker. Domma.effects.strobe(null, { preset: 'concert' })
police
Two beams red + blue, fast narrow sweep, hard flicker. Domma.effects.strobe(null, { preset: 'police' })
searchlight
Bottom corners, smooth rotating white beams, no flicker. Domma.effects.strobe(null, { preset: 'searchlight' })
scanner
Thin cyan beams, quick sweep, no flicker. Domma.effects.strobe(null, { preset: 'scanner' })
mood
Slow wide pastel beams, smooth glow, no flicker. Domma.effects.strobe(null, { preset: 'mood' })
<dt>club</dt>
<dd>Four-corner crisscross, multi-colour, fast flicker. <code>Domma.effects.strobe(null, { preset: 'club' })</code></dd>
<dt>concert</dt>
<dd>Two top beams fanning wide, rainbow, medium flicker. <code>Domma.effects.strobe(null, { preset: 'concert' })</code></dd>
<dt>police</dt>
<dd>Two beams red + blue, fast narrow sweep, hard flicker. <code>Domma.effects.strobe(null, { preset: 'police' })</code></dd>
<dt>searchlight</dt>
<dd>Bottom corners, smooth rotating white beams, no flicker. <code>Domma.effects.strobe(null, { preset: 'searchlight' })</code></dd>
<dt>scanner</dt>
<dd>Thin cyan beams, quick sweep, no flicker. <code>Domma.effects.strobe(null, { preset: 'scanner' })</code></dd>
<dt>mood</dt>
<dd>Slow wide pastel beams, smooth glow, no flicker. <code>Domma.effects.strobe(null, { preset: 'mood' })</code></dd>

Demo: Preset Picker

Click a preset to select it, then press Start to light up the stage below. Nothing runs until you press Start.

Demo: Preset Picker
🔦
Pick a preset and press Start
Stopped
<div class="preset-swatches" id="preset-picker"></div>
<div class="strobe-container" id="strobe-container-1">
    <div class="strobe-container-label">
        <div style="font-size: 2rem; margin-bottom: 0.5rem;">🔦</div>
        <div>Pick a preset and press Start</div>
    </div>
</div>
<span id="status-1" class="status-badge stopped"><span class="status-dot"></span><span class="status-text">Stopped</span></span>
<button type="button" class="btn btn-primary" id="start-1">Start</button>
<button type="button" class="btn btn-secondary" id="pause-1">Pause</button>
<button type="button" class="btn btn-secondary" id="resume-1">Resume</button>
<button type="button" class="btn btn-danger" id="stop-1">Stop</button>
/**
 * Demo: Preset Picker - from public/showcase/effects/strobe.
 */
export function mount(root) {
        // ── Status badge helper ───────────────────────────────────────────────
    function setStatus($badge, state) {
          if (!$badge || !$badge.length) return;
          const labels = { running: 'Running', paused: 'Paused', stopped: 'Stopped' };
          $badge.attr('class', 'status-badge ' + state);
          $badge.find('.status-dot')
                .attr('class', 'status-dot' + (state === 'running' ? ' pulse-dot' : ''));
          $badge.find('.status-text').text(labels[state] || state);
        }
        // ── Preset swatch colours (for the picker dots only) ─────────────────
    const presetColours = {
          club:        ['#ff0066', '#00e5ff', '#ffee00', '#00ff88'],
          concert:     ['#ef4444', '#f97316', '#facc15', '#22c55e', '#0ea5e9', '#a855f7'],
          police:      ['#ff2222', '#2244ff'],
          searchlight: ['#ffffff', '#e5e7eb'],
          scanner:     ['#22d3ee', '#06b6d4'],
          mood:        ['#fbcfe8', '#bae6fd', '#bbf7d0', '#ddd6fe']
        };
    let activePreset = 'club';
    function renderPresetCard(name) {
          const dots = presetColours[name].map(c => `<span style="background:${c}"></span>`).join('');
          return `
            <div class="preset-card${name === activePreset ? ' active' : ''}" data-preset="${name}">
              <div class="preset-name">${name}</div>
              <div class="preset-dots">${dots}</div>
            </div>`;
        }
        // Note: pass {safe: false} because Domma's default html() sanitiser strips
        // both inline `style` attributes and `data-*` attributes - we'd lose the
        // swatch colours AND the data-preset hook the click handler reads.
    const pickerMarkup = Object.keys(presetColours).map(renderPresetCard).join('');
    $(root).find('#preset-picker').html(pickerMarkup, { safe: false });
    $(root).find('#preset-picker').on('click', (e) => {
          const card = e.target.closest('.preset-card');
          if (!card) return;
          $('#preset-picker .preset-card').removeClass('active');
          card.classList.add('active');
          activePreset = card.getAttribute('data-preset');
        });
        // ── Demo 1: Preset picker ────────────────────────────────────────────
    let ctrl1 = null;
    const badge1 = $(root).find('#status-1');
    $(root).find('#start-1').on('click', () => {
          if (ctrl1) ctrl1.destroy();
          ctrl1 = Domma.effects.strobe('#strobe-container-1', {
            preset: activePreset,
            respectMotionPreference: false
          });
          setStatus(badge1, 'running');
        });
    $(root).find('#pause-1').on('click', () => { if (ctrl1) { ctrl1.pause(); setStatus(badge1, 'paused'); } });
    $(root).find('#resume-1').on('click', () => { if (ctrl1) { ctrl1.resume(); setStatus(badge1, 'running'); } });
    $(root).find('#stop-1').on('click', () => { if (ctrl1) { ctrl1.stop(); ctrl1 = null; setStatus(badge1, 'stopped'); } });
}
Domma.effects.strobe('#strobe-container-1', {
  preset: 'club'   // the selected preset
});

Demo: Build Your Own

Configure the beams from scratch, then press Apply.

Demo: Build Your Own
100 10 1 16 0.7
<label>Origins</label>
<label style="min-width:auto;"><input type="checkbox" class="opt-origin" value="tl" checked=""> Top-left</label>
<label style="min-width:auto;"><input type="checkbox" class="opt-origin" value="tr" checked=""> Top-right</label>
<label style="min-width:auto;"><input type="checkbox" class="opt-origin" value="bl" checked=""> Bottom-left</label>
<label style="min-width:auto;"><input type="checkbox" class="opt-origin" value="br" checked=""> Bottom-right</label>
<label>Motion</label>
<select class="form-control" id="opt-motion" style="max-width:180px;">
    <option value="sweep" selected="">sweep</option>
    <option value="rotate">rotate</option>
</select>
<label style="min-width:auto;"><input type="checkbox" id="opt-flicker" checked=""> Flicker</label>
<label>Sweep arc (°)</label>
<input type="range" id="opt-arc" min="20" max="180" step="5" value="100">
<span class="readout" id="val-arc">100</span>
<label>Flicker (Hz)</label>
<input type="range" id="opt-hz" min="1" max="15" step="1" value="10">
<span class="readout" id="val-hz">10</span>
<label>Speed</label>
<input type="range" id="opt-speed" min="0.2" max="3" step="0.1" value="1">
<span class="readout" id="val-speed">1</span>
<label>Beam width (°)</label>
<input type="range" id="opt-beam" min="4" max="40" step="1" value="16">
<span class="readout" id="val-beam">16</span>
<label>Intensity</label>
<input type="range" id="opt-intensity" min="0.2" max="1" step="0.05" value="0.7">
<span class="readout" id="val-intensity">0.7</span>
<label>Colours</label>
<input type="color" id="colour-a" value="#ffffff">
<input type="color" id="colour-b" value="#38bdf8">
/**
 * Demo: Build Your Own - from public/showcase/effects/strobe.
 */
export function mount(root) {
        // ── Demo 2: Build your own ───────────────────────────────────────────
    const readouts = ['arc', 'hz', 'speed', 'beam', 'intensity'];
    readouts.forEach(name => {
          $(`#opt-${name}`).on('input', (e) => { $(`#val-${name}`).text(e.target.value); });
        });
    let ctrl2 = null;
    $(root).find('#apply-2').on('click', () => {
          if (ctrl2) ctrl2.destroy();
          const origins = $('.opt-origin:checked').get().map(c => c.value);
          ctrl2 = Domma.effects.strobe('#strobe-container-2', {
            origins: origins.length ? origins : ['tl'],
            motion: $(root).find('#opt-motion').val(),
            flicker: $(root).find('#opt-flicker').prop('checked'),
            sweepArc: parseFloat($(root).find('#opt-arc').val()),
            hz: parseFloat($(root).find('#opt-hz').val()),
            speed: parseFloat($(root).find('#opt-speed').val()),
            beamWidth: parseFloat($(root).find('#opt-beam').val()),
            intensity: parseFloat($(root).find('#opt-intensity').val()),
            colours: [$(root).find('#colour-a').val(), $(root).find('#colour-b').val()],
            respectMotionPreference: false
          });
        });
}

With flicker on, setting the Hz above 5 logs a console warning - the value is never clamped, so keep it low for safety.

Demo: Build Your Own
Press Apply to light the stage with your settings
<div class="strobe-container mt-3" id="strobe-container-2">
    <div class="strobe-container-label">
        <div>Press Apply to light the stage with your settings</div>
    </div>
</div>
<button type="button" class="btn btn-primary" id="apply-2">Apply</button>
<button type="button" class="btn btn-danger" id="stop-2">Stop</button>
/**
 * Demo: Build Your Own - from public/showcase/effects/strobe.
 */
export function mount(root) {
    let ctrl2 = null;
    $(root).find('#stop-2').on('click', () => { if (ctrl2) { ctrl2.stop(); ctrl2 = null; } });
}

Demo: Full-Page

Pass null as the selector to light the entire viewport. This affects the whole page - it uses the preset selected in the Preset Picker above (or club by default), so use the Stop button to end it. It never autoplays and never stops on its own here.

Demo: Full-Page
Stopped
<span id="status-3" class="status-badge stopped"><span class="status-dot"></span><span class="status-text">Stopped</span></span>
<button type="button" class="btn btn-primary" id="start-3">Start Full-Page Lighting</button>
<button type="button" class="btn btn-danger" id="stop-3">Stop</button>
/**
 * Demo: Full-Page - from public/showcase/effects/strobe.
 */
export function mount(root) {
        // ── Status badge helper ───────────────────────────────────────────────
    function setStatus($badge, state) {
          if (!$badge || !$badge.length) return;
          const labels = { running: 'Running', paused: 'Paused', stopped: 'Stopped' };
          $badge.attr('class', 'status-badge ' + state);
          $badge.find('.status-dot')
                .attr('class', 'status-dot' + (state === 'running' ? ' pulse-dot' : ''));
          $badge.find('.status-text').text(labels[state] || state);
        }
    let activePreset = 'club';
        // ── Demo 3: Full-page ────────────────────────────────────────────────
    let ctrl3 = null;
    const badge3 = $(root).find('#status-3');
    $(root).find('#start-3').on('click', () => {
          if (ctrl3) ctrl3.destroy();
          ctrl3 = Domma.effects.strobe(null, {
            preset: activePreset,
            respectMotionPreference: false
          });
          setStatus(badge3, 'running');
        });
    $(root).find('#stop-3').on('click', () => { if (ctrl3) { ctrl3.stop(); ctrl3 = null; setStatus(badge3, 'stopped'); } });
    // The full-page effect goes with the demo: stopped when it is reset or left.
    return () => { if (ctrl3) ctrl3.destroy(); };
}
const lights = Domma.effects.strobe(null, {
  preset: 'club'
});
// ...later
lights.stop();