Twinkle Effect

Canvas-based twinkling stars animation. Works as a full-page ambient overlay or scoped to any container.

Domma.effects.twinkle()

Creates an ambient canvas-based starfield animation. Pass null (or omit the selector) for a full-page fixed overlay, or provide a selector to scope stars inside a specific container. Stars oscillate in brightness using a sinusoidal function and are drawn as 4-pointed shapes with a centre glow.

// Full-page overlay
const stars = Domma.effects.twinkle(null);

// Container-scoped stars
const stars = Domma.effects.twinkle('#hero-section', {
  count: 80,
  colour: 'rgba(200, 220, 255, 0.9)',
  shape: 'circle'
});

// Control methods
stars.pause();
stars.resume();
stars.stop();
stars.restart();
stars.destroy();

Options

Option Type Default Description
count number 100 Number of stars to render
minSize number 1 Minimum star radius in pixels
maxSize number 3 Maximum star radius in pixels
twinkleSpeed number 0.003 Base oscillation speed per frame (higher = faster twinkling)
colour string 'rgba(255, 240, 200, 1)' Star colour (any CSS colour value)
zIndex number 1 Canvas stacking order
shape 'star' | 'circle' 'star' 'star' draws a 4-pointed shape with glow; 'circle' draws a soft dot
respectMotionPreference boolean true Honour prefers-reduced-motion OS setting

Control Methods

const stars = Domma.effects.twinkle('#section');

stars.pause();      // Pause rendering (stars freeze in place)
stars.resume();     // Resume from where it paused
stars.stop();       // Stop and cancel animation frame
stars.restart();    // Recreate stars and restart
stars.destroy();    // Stop + remove canvas from DOM
stars.isRunning();  // true when animating (not paused)
stars.isPaused();   // true when paused mid-animation

Demo: Container-Scoped Stars

Stars rendered inside a dark section element. The canvas is sized to the container and uses position: absolute so content above it remains interactive.

Demo: Container-Scoped Stars
✦
Stars are rendered in this container
Click Start to initialise
Stopped
<div class="star-container" id="star-container-1">
    <div class="star-container-label">
        <div style="font-size: 2rem; margin-bottom: 0.5rem;">✦</div>
        <div>Stars are rendered in this container</div>
        <div style="font-size: 0.85rem; opacity: 0.7; margin-top: 0.3rem;">Click Start to initialise</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-secondary" id="restart-1">Restart</button>
<button type="button" class="btn btn-danger" id="destroy-1">Destroy</button>
/**
 * Demo: Container-Scoped Stars - from public/showcase/effects/twinkle.
 */
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);
        }
        // ── Demo 1: Container-scoped ──────────────────────────────────────────
    let ctrl1 = null;
    const badge1 = $(root).find('#status-1');
    $(root).find('#start-1').on('click', () => {
          if (ctrl1) ctrl1.destroy();
          ctrl1 = Domma.effects.twinkle('#star-container-1', {
            count: 80,
            minSize: 1,
            maxSize: 3,
            colour: 'rgba(255, 240, 200, 0.9)',
            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('#restart-1').on('click', () => {
          if (ctrl1) { ctrl1.restart(); setStatus(badge1, 'running'); }
        });
    $(root).find('#destroy-1').on('click', () => {
          if (ctrl1) { ctrl1.destroy(); ctrl1 = null; setStatus(badge1, 'stopped'); }
        });
}
const stars = Domma.effects.twinkle('#star-container', {
  count: 80,
  minSize: 1,
  maxSize: 3,
  colour: 'rgba(255, 240, 200, 0.9)',
  respectMotionPreference: false
});

Demo: Full-Page Overlay

Pass null as the selector to create a position: fixed canvas overlay that covers the entire viewport. Stars sit behind page content via pointer-events: none. This is the mode used on the Domma splash page.

Demo: Full-Page Overlay
Stopped
<span id="status-2" 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-2">Start Full-Page</button>
<button type="button" class="btn btn-secondary" id="pause-2">Pause</button>
<button type="button" class="btn btn-secondary" id="resume-2">Resume</button>
<button type="button" class="btn btn-danger" id="destroy-2">Destroy</button>
/**
 * Demo: Full-Page Overlay - from public/showcase/effects/twinkle.
 */
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);
        }
        // ── Demo 2: Full-page overlay ─────────────────────────────────────────
    let ctrl2 = null;
    const badge2 = $(root).find('#status-2');
    $(root).find('#start-2').on('click', () => {
          if (ctrl2) ctrl2.destroy();
          ctrl2 = Domma.effects.twinkle(null, {
            count: 120,
            colour: 'rgba(255, 240, 200, 0.85)',
            zIndex: 0,
            respectMotionPreference: false
          });
          setStatus(badge2, 'running');
        });
    $(root).find('#pause-2').on('click', () => {
          if (ctrl2) { ctrl2.pause(); setStatus(badge2, 'paused'); }
        });
    $(root).find('#resume-2').on('click', () => {
          if (ctrl2) { ctrl2.resume(); setStatus(badge2, 'running'); }
        });
    $(root).find('#destroy-2').on('click', () => {
          if (ctrl2) { ctrl2.destroy(); ctrl2 = null; setStatus(badge2, 'stopped'); }
        });
}
// null selector creates a full-page fixed canvas
const stars = Domma.effects.twinkle(null, {
  count: 120,
  colour: 'rgba(255, 240, 200, 0.85)',
  zIndex: 0   // behind all page content
});

Demo: Customisation

Adjust options and click Apply to see the changes.

Demo: Customisation
80 1 3 0.003 Colour preset:
Adjust sliders above and click Apply
<label>Count</label>
<input type="range" id="opt-count" min="20" max="300" value="80">
<span id="val-count">80</span>
<label>Min Size</label>
<input type="range" id="opt-min-size" min="0.5" max="3" step="0.5" value="1">
<span id="val-min-size">1</span>
<label>Max Size</label>
<input type="range" id="opt-max-size" min="1" max="8" step="0.5" value="3">
<span id="val-max-size">3</span>
<label>Twinkle Speed</label>
<input type="range" id="opt-speed" min="0.001" max="0.02" step="0.001" value="0.003">
<span id="val-speed">0.003</span>
<strong style="font-size: 0.9rem;">Colour preset:</strong>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;">
    <button type="button" class="btn btn-sm colour-btn active" data-colour="rgba(255, 240, 200, 1)">Warm White</button>
    <button type="button" class="btn btn-sm colour-btn" data-colour="rgba(180, 210, 255, 1)">Cool Blue</button>
    <button type="button" class="btn btn-sm colour-btn" data-colour="rgba(200, 255, 200, 1)">Soft Green</button>
    <button type="button" class="btn btn-sm colour-btn" data-colour="rgba(255, 180, 200, 1)">Rose</button>
    <button type="button" class="btn btn-sm colour-btn" data-colour="rgba(255, 200, 120, 1)">Amber</button>
</div>
<div class="star-container mt-4" id="star-container-3" style="min-height: 200px;">
    <div class="star-container-label">
        <div style="font-size: 0.9rem; opacity: 0.8;">Adjust sliders above and click Apply</div>
    </div>
</div>
<button type="button" class="btn btn-primary" id="apply-3">Apply Options</button>
<button type="button" class="btn btn-danger" id="destroy-3">Destroy</button>
/**
 * Demo: Customisation - from public/showcase/effects/twinkle.
 */
export function mount(root) {
        // ── Demo 3: Customisation ─────────────────────────────────────────────
    let ctrl3 = null;
    let selectedColour = 'rgba(255, 240, 200, 1)';
    _.each(['count', 'min-size', 'max-size', 'speed'], id => {
          const $el = $('#opt-' + id);
          const $val = $('#val-' + id);
          if ($el.length && $val.length) {
            $el.on('input', () => $val.text($el.val()));
          }
        });
    $('.colour-btn').on('click', function () {
          selectedColour = $(this).attr('data-colour');
          $('.colour-btn').removeClass('active');
          $(this).addClass('active');
        });
    $(root).find('#apply-3').on('click', () => {
          if (ctrl3) ctrl3.destroy();
          ctrl3 = Domma.effects.twinkle('#star-container-3', {
            count: parseInt($(root).find('#opt-count').val(), 10),
            minSize: parseFloat($(root).find('#opt-min-size').val()),
            maxSize: parseFloat($(root).find('#opt-max-size').val()),
            twinkleSpeed: parseFloat($(root).find('#opt-speed').val()),
            colour: selectedColour,
            respectMotionPreference: false
          });
        });
    $(root).find('#destroy-3').on('click', () => {
          if (ctrl3) { ctrl3.destroy(); ctrl3 = null; }
        });
}

Demo: Shape Variants

The shape option controls how each star is drawn. 'star' renders a 4-pointed shape with a centre glow. 'circle' renders a soft filled dot.

Demo: Shape Variants

shape: 'star' (default)

4-pointed stars with glow

shape: 'circle'

Soft circular dots
<div>
    <p class="text-center mb-2"><strong>shape: 'star'</strong> (default)</p>
    <div class="star-container" id="shape-star" style="min-height: 160px;">
        <div class="star-container-label" style="font-size: 0.85rem; opacity: 0.7;">4-pointed stars with glow</div>
    </div>
</div>
<div>
    <p class="text-center mb-2"><strong>shape: 'circle'</strong></p>
    <div class="star-container" id="shape-circle" style="min-height: 160px;">
        <div class="star-container-label" style="font-size: 0.85rem; opacity: 0.7;">Soft circular dots</div>
    </div>
</div>
<button type="button" class="btn btn-primary" id="start-shapes">Start Both</button>
<button type="button" class="btn btn-danger" id="destroy-shapes">Destroy Both</button>
/**
 * Demo: Shape Variants - from public/showcase/effects/twinkle.
 */
export function mount(root) {
        // ── Demo 4: Shape variants ────────────────────────────────────────────
    let ctrlStar = null;
    let ctrlCircle = null;
    $(root).find('#start-shapes').on('click', () => {
          if (ctrlStar) ctrlStar.destroy();
          if (ctrlCircle) ctrlCircle.destroy();
          ctrlStar = Domma.effects.twinkle('#shape-star', {
            count: 60,
            shape: 'star',
            respectMotionPreference: false
          });
          ctrlCircle = Domma.effects.twinkle('#shape-circle', {
            count: 60,
            shape: 'circle',
            respectMotionPreference: false
          });
        });
    $(root).find('#destroy-shapes').on('click', () => {
          if (ctrlStar) { ctrlStar.destroy(); ctrlStar = null; }
          if (ctrlCircle) { ctrlCircle.destroy(); ctrlCircle = null; }
        });
}
// 4-pointed star (default)
Domma.effects.twinkle('#shape-star', { shape: 'star', count: 60 });

// Circular dot
Domma.effects.twinkle('#shape-circle', { shape: 'circle', count: 60 });