Docs / Web components

Embed the timer

The player and builder are standard web components. Embed the player on any page — no framework required.

  • No build step
  • Sends no data
  • Themed with CSS variables

Play a sequence

Copy the complete HTML example into your page. The hosted browser module includes its dependencies; no package installation or build tool is needed. Set the sequence property and let the visitor press Start so the browser can enable sound.

By default the player ends with a small “Powered by EigenTime Timer” link that opens this site in a new tab. It is a plain link and requests nothing until clicked. Add the no-attribution attribute to hide it.

player-example.htmlOpen example
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="robots" content="noindex">
  <title>My timer</title>
  <style>
    body { margin: 0 auto; padding: 1rem; max-width: 48rem; font: 16px system-ui, sans-serif; }
    sequence-timer-player, sequence-timer-builder { display: block; margin: 1rem 0; }
  </style>
</head>
<body>
  <h1>My timer</h1>
  <sequence-timer-player locale="en-US"></sequence-timer-player>
  <p data-status role="status"></p>
  <script type="module">
    import 'https://timer.eigentime.org/embed/v1/player.js';
    const sequence = {
      schemaVersion: 1,
      id: 'my-timer',
      metadata: { name: { 'en-US': 'My timer' } },
      children: [
        { type: 'phase', id: 'prepare', role: 'prepare', durationMs: 5000 },
        {
          type: 'group', id: 'round', repeat: 4,
          children: [
            {
              type: 'phase', id: 'work', role: 'work', durationMs: 30000,
              cues: { start: 'beep', countdownSeconds: 3 },
            },
            { type: 'phase', id: 'rest', role: 'rest', durationMs: 10000 },
          ],
        },
      ],
    };
    const player = document.querySelector('sequence-timer-player');
    player.sequence = sequence;
    player.addEventListener('complete', () => {
      document.querySelector('[data-status]').textContent = 'Sequence complete.';
    });
    // Playback starts only when the visitor presses Start.
  </script>
</body>
</html>

Builder and player

The builder and player load separately. The builder’s start event supplies a validated sequence, which the example passes to the same player component.

builder-example.htmlOpen example
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="robots" content="noindex">
  <title>My timer</title>
  <style>
    body { margin: 0 auto; padding: 1rem; max-width: 48rem; font: 16px system-ui, sans-serif; }
    sequence-timer-player, sequence-timer-builder { display: block; margin: 1rem 0; }
  </style>
</head>
<body>
  <h1>My timer</h1>
  <sequence-timer-builder locale="en-US"></sequence-timer-builder>
  <sequence-timer-player locale="en-US"></sequence-timer-player>
  <p data-status role="status"></p>
  <script type="module">
    import 'https://timer.eigentime.org/embed/v1/player.js';
    import 'https://timer.eigentime.org/embed/v1/builder.js';
    const sequence = {
      schemaVersion: 1,
      id: 'my-timer',
      metadata: { name: { 'en-US': 'My timer' } },
      children: [
        { type: 'phase', id: 'prepare', role: 'prepare', durationMs: 5000 },
        {
          type: 'group', id: 'round', repeat: 4,
          children: [
            {
              type: 'phase', id: 'work', role: 'work', durationMs: 30000,
              cues: { start: 'beep', countdownSeconds: 3 },
            },
            { type: 'phase', id: 'rest', role: 'rest', durationMs: 10000 },
          ],
        },
      ],
    };
    const player = document.querySelector('sequence-timer-player');
    player.sequence = sequence;
    player.addEventListener('complete', () => {
      document.querySelector('[data-status]').textContent = 'Sequence complete.';
    });
    const builder = document.querySelector('sequence-timer-builder');
    builder.sequence = sequence;
    builder.addEventListener('start', async (event) => {
      player.sequence = event.detail.sequence;
      await player.updateComplete;
      player.start();
      player.scrollIntoView({ block: 'start' });
    });
    // Playback starts only when the visitor presses Start.
  </script>
</body>
</html>

Events

Listen for statechange, phasechange, cue and complete events to drive your own UI or analytics — the component sends nothing anywhere on its own.

events.js
const player = document.querySelector('sequence-timer-player');

player.addEventListener('statechange', (event) => {
  // 'idle' | 'running' | 'paused' | 'completed'
  console.log(event.detail.state);
});

player.addEventListener('phasechange', (event) => {
  const { step } = event.detail;
  console.log(step.phase.role, step.durationMs);
});

player.addEventListener('cue', (event) => {
  console.log(event.detail.sound, event.detail.secondsRemaining);
});

player.addEventListener('complete', () => {
  console.log('done');
});

Theming

Colors and sizes are exposed as CSS custom properties (--sequence-primary, --sequence-work, …), so you can theme it without touching the shadow DOM. Set --sequence-primary and --sequence-on-primary together to keep button text readable.

theme.css
sequence-timer-player {
  color-scheme: dark; /* light, dark, or inherit the page's */
  --sequence-primary: #60a5fa;
  --sequence-on-primary: #0b1220;
  --sequence-work: #60a5fa;
  --sequence-rest: #2dd4bf;
}

Delivery and self-hosting

Browser modules live under /embed/v1/, use standard JavaScript modules and allow cross-origin imports. Importing only player.js does not load the builder. To self-host, copy the entire apps/web/dist/embed/v1/ build directory to your HTTPS site, retain the shared chunks and update the import URLs. The v1 URLs receive compatible updates; keep files from the same release together.

The repository’s @sequence-timer/* packages are currently private workspace packages, not public npm releases. External sites should use these browser modules.

Embedded components do not install an offline worker, read site preferences, save sequences automatically or send analytics. The host controls saving and sharing. Installation and offline support belong only to the online tool.