Doku / Web Components

Den Timer einbetten

Player und Editor sind Standard-Web-Components. Bette den Player in jede Seite ein – ganz ohne Framework.

  • Kein Build-Schritt
  • Sendet keine Daten
  • Gestaltung per CSS-Variablen

Einen Ablauf abspielen

Kopiere das vollständige HTML-Beispiel in deine Seite. Das gehostete Browser-Modul bringt seine Abhängigkeiten mit; du brauchst weder eine Paketinstallation noch ein Build-Tool. Setze die Eigenschaft sequence und lass die Besucher auf Start drücken, damit der Browser den Ton freigibt.

Standardmäßig endet der Player mit einem kleinen Link „Powered by EigenTime Timer“, der diese Website in einem neuen Tab öffnet. Es ist ein einfacher Link, der erst beim Anklicken etwas anfragt. Mit dem Attribut no-attribution blendest du ihn aus.

player-example.htmlBeispiel öffnen
<!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.';
    });
    // Exit stops the run and leaves fullscreen; the page decides what follows,
    // such as closing a dialog. Pages with nothing to do can hide ::part(exit).
    player.addEventListener('exit', () => {
      document.querySelector('[data-status]').textContent = 'Stopped. What Exit does next is up to your page.';
    });
    // Playback starts only when the visitor presses Start.
  </script>
</body>
</html>

Editor und Player

Editor und Player werden getrennt geladen. Das start-Ereignis des Editors liefert einen geprüften Ablauf, den das Beispiel an dieselbe Player-Komponente weitergibt.

builder-example.htmlBeispiel öffnen
<!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.';
    });
    // Exit stops the run and leaves fullscreen; the page decides what follows,
    // such as closing a dialog. Pages with nothing to do can hide ::part(exit).
    player.addEventListener('exit', () => {
      document.querySelector('[data-status]').textContent = 'Stopped. What Exit does next is up to your page.';
    });
    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>

Ereignisse

Reagiere auf die Ereignisse statechange, phasechange, cue und complete, um deine eigene Oberfläche oder Statistik zu steuern – die Komponente selbst sendet nichts.

Die Schaltfläche „Beenden“ stoppt den Ablauf, verlässt den Vollbildmodus und sendet ein exit-Ereignis; was danach passiert, entscheidet deine Seite, etwa einen Dialog schließen oder zum Editor zurückkehren. Braucht deine Seite das nicht, blende die Schaltfläche mit ::part(exit) { display: none; } aus.

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');
});

player.addEventListener('exit', () => {
  // Stopped and out of fullscreen: close your dialog, go back, …
});

Gestaltung

Farben und Größen sind als CSS-Custom-Properties verfügbar (--sequence-primary, --sequence-work, …), sodass du das Aussehen anpassen kannst, ohne das Shadow DOM anzufassen. Setze --sequence-primary und --sequence-on-primary immer zusammen, damit die Schrift auf den Schaltflächen lesbar bleibt.

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;
}

Größe und Anordnung

Der Player richtet sich nach seiner eigenen Breite: Gib dem Element eine Breite, und Ring und Schrift skalieren mit. Ab 800px Breite steht der Ring links, Anzeigen und Schaltflächen rechts. layout="stacked" ordnet immer untereinander an; layout="split" ab 576px nebeneinander, wo die Schaltflächen noch Platz haben.

--sequence-dial-size begrenzt den Ring auf der Seite (standardmäßig 440px), und mit clamp() lässt er sich an die Fensterhöhe koppeln. Im Vollbild passt der Ring immer auf den Bildschirm.

Für feinere Anpassungen gestaltest du die Teile mit ::part(): dial, phase, time, note, stats, next, controls, toolbar, exit und attribution. no-controls blendet alle Schaltflächen aus, sodass deine Seite die Wiedergabe selbst steuern kann.

Anordnung
Farben
Ausblenden
embed.html
<sequence-timer-player locale="de-DE"></sequence-timer-player>
<style>
  sequence-timer-player {
    max-width: 720px;
  }
</style>

Auslieferung und Selbsthosting

Die Browser-Module liegen unter /embed/v1/ und erlauben Importe von anderen Ursprüngen. Wer nur player.js importiert, lädt den Editor nicht mit. Am einfachsten nutzt du diese URLs direkt: Dateien unter v1 werden an Ort und Stelle aktualisiert, nur mit abwärtskompatiblen Änderungen. Ein npm-Paket gibt es noch nicht.

Um eine eigene Kopie zu hosten, lade die Module mit den Befehlen unten in einen Ordner deiner Website: player.js, builder.js falls du den Editor nutzt, das gemeinsame Modul (sein Name enthält einen Versions-Hash) und die Lizenzen der Drittanbieter. Lass dann den Import im Beispiel auf deine Kopie zeigen, zum Beispiel /sequence-timer/player.js.

Eine Kopie aktualisiert sich nicht von selbst. Lade zum Aktualisieren den ganzen Ordner neu, statt eine einzelne Datei zu ersetzen: Die Dateien importieren einander über ihre gehashten Namen, deshalb laden Dateien aus verschiedenen Versionen nicht zusammen.

download.sh
# Download into sequence-timer/, then put the whole folder on your site
mkdir sequence-timer && cd sequence-timer
base=https://timer.eigentime.org/embed/v1
curl -fO "$base/player.js" -fO "$base/builder.js" -fO "$base/THIRD-PARTY-LICENSES.txt"
# The shared module's name changes with each release; read it from the imports
for file in $(grep -ho '"\./[^"]*\.js"' player.js builder.js | tr -d '"' | sort -u); do
  curl -fO "$base/${file#./}"
done

Eingebettete Komponenten installieren keinen Offline-Worker, lesen keine Website-Einstellungen, speichern Abläufe nicht automatisch und senden keine Statistik. Speichern und Teilen steuert die einbettende Seite. Installation und Offline-Betrieb gibt es nur im Online-Tool.