Design · Bausteine-Dossier

Website- & App-Elemente · Text

Text-Effekte

Animierte Überschriften und Lauftexte — nach Vorbildern von reactbits.dev als Clean-Room-Vanilla nachgebaut (kein fremder Code). Jeder Effekt läuft live, Akzentfarbe per --akzent, barrierefrei mit prefers-reduced-motion-Reset.

Split-Text (Buchstaben-Einflug)

Pol 1 · Craft Anlass: Headline-Auftritt

Die Überschrift wird per JS in einzelne Buchstaben zerlegt, die beim Erscheinen gestaffelt von unten hochgleiten und einblenden (Stagger über --i und transition-delay); der Replay-Knopf löst den Einflug erneut aus. Das aria-label hält die Headline für Screenreader lesbar, bei reduzierter Bewegung steht sie sofort im Endzustand. Vorbild: reactbits SplitText.

Handwerk, das man sieht.

<div class="te1-stage">
  <h2 class="te1-headline" data-splittext aria-label="Handwerk, das man sieht.">Handwerk, das man sieht.</h2>
  <button type="button" class="te1-replay" data-splittext-replay>
    <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2"
         stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v4h4"/>
    </svg>
    Erneut abspielen
  </button>
</div>

<style>
.te1-stage{width:100%;text-align:center}
.te1-headline{margin:0;font-size:clamp(1.6rem,5vw,2.6rem);font-weight:800;line-height:1.12;
  letter-spacing:-.01em;color:#f4f4f5}
.te1-word{display:inline-block;white-space:nowrap}          /* Wort bleibt zusammen, Umbruch nur zwischen Wörtern */
.te1-char{display:inline-block;will-change:transform,opacity}
/* Startzustand: jeder Buchstabe unten + unsichtbar */
.te1-stage .te1-char{opacity:0;transform:translateY(.9em)}
/* Endzustand nach Trigger: hochgleiten + einblenden, gestaffelt über --i */
.te1-stage.is-in .te1-char{opacity:1;transform:translateY(0);
  transition:transform .5s cubic-bezier(.22,1,.36,1),opacity .5s cubic-bezier(.22,1,.36,1);
  transition-delay:calc(var(--i) * 35ms)}
.te1-replay{display:inline-flex;align-items:center;gap:.45rem;margin-top:1.4rem;
  padding:.5rem .95rem;font:inherit;font-size:.85rem;color:#e9e9ee;background:#1c1c22;
  border:1px solid #26262c;border-radius:99px;cursor:pointer;
  transition:border-color .2s ease,background .2s ease,color .2s ease}
.te1-replay svg{color:var(--akzent,#7c87ff);transition:transform .4s ease}
.te1-replay:hover,.te1-replay:focus-visible{border-color:var(--akzent,#7c87ff);background:#202028;color:#f4f4f5}
.te1-replay:hover svg{transform:rotate(-90deg)}
.te1-replay:focus-visible{outline:2px solid var(--akzent,#7c87ff);outline-offset:2px}
@media (prefers-reduced-motion:reduce){
  .te1-stage .te1-char{opacity:1;transform:none}                 /* Endzustand direkt zeigen */
  .te1-stage.is-in .te1-char{transition:none;transition-delay:0s}
  .te1-replay,.te1-replay svg{transition:none}
  .te1-replay:hover svg{transform:none}
}
</style>

<script>
(function(){
  var stages = document.querySelectorAll('.te1-stage');
  if(!stages.length) return;
  var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion:reduce)').matches;

  stages.forEach(function(stage){
    var h = stage.querySelector('[data-splittext]');
    var btn = stage.querySelector('[data-splittext-replay]');
    if(!h || h.dataset.split === '1') return;   /* mehrfach-sicher */

    /* Text in Wörter, Wörter in Buchstaben zerlegen; Leerzeichen als echte
       Textknoten erhalten, damit der Zeilenumbruch weiter funktioniert */
    var text = h.textContent, idx = 0;
    h.textContent = '';
    text.split(/(\s+)/).forEach(function(part){
      if(/^\s+$/.test(part)){ h.appendChild(document.createTextNode(part)); return; }
      var word = document.createElement('span');
      word.className = 'te1-word';
      part.split('').forEach(function(ch){
        var s = document.createElement('span');
        s.className = 'te1-char';
        s.style.setProperty('--i', idx++);
        s.textContent = ch;
        word.appendChild(s);
      });
      h.appendChild(word);
    });
    h.dataset.split = '1';

    function play(){
      if(reduce){ stage.classList.add('is-in'); return; }
      stage.classList.remove('is-in');
      void h.offsetWidth;                 /* Reflow erzwingen -> Stagger neu starten */
      stage.classList.add('is-in');
    }

    /* Erstes Abspielen, sobald die Headline sichtbar wird */
    if('IntersectionObserver' in window && !reduce){
      var io = new IntersectionObserver(function(entries){
        entries.forEach(function(e){ if(e.isIntersecting){ play(); io.disconnect(); } });
      }, { threshold: 0.5 });
      io.observe(stage);
    } else {
      play();
    }
    if(btn) btn.addEventListener('click', play);
  });
})();
</script>

Blur-Text (Schärfe-Einblendung)

Pol 1 · Craft Anlass: Headline-Auftritt

Wörter fahren gestaffelt von unscharf zu scharf ein und geben der Überschrift einen ruhigen, hochwertigen Auftritt. Vorbild: reactbits BlurText.

Willkommen

Klarheit entsteht Wort für Wort.

Jedes Wort schält sich sauber aus der Unschärfe.

<style>
.te2-stage{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1.4rem;min-height:230px;padding:2.4rem 1.6rem;
  background:#1c1c22;border:1px solid #26262c;border-radius:14px;
  font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  overflow:hidden;
}
.te2-eyebrow{
  font-size:.72rem;letter-spacing:.22em;text-transform:uppercase;
  color:var(--akzent,#7c87ff);font-weight:600;
}
.te2-headline{
  margin:0;text-align:center;line-height:1.15;
  font-size:clamp(1.6rem,4.4vw,2.4rem);font-weight:700;
  color:#f4f4f5;letter-spacing:-.01em;
  max-width:22ch;
}
.te2-word{
  display:inline-block;white-space:pre;
  opacity:0;filter:blur(12px);transform:translateY(.18em);
  transition:opacity .5s cubic-bezier(.22,.61,.36,1),
             filter .5s cubic-bezier(.22,.61,.36,1),
             transform .5s cubic-bezier(.22,.61,.36,1);
  transition-delay:var(--te2-delay,0ms);
}
.te2-word.te2-in{opacity:1;filter:blur(0);transform:translateY(0);}
.te2-sub{
  margin:0;text-align:center;color:#a1a1aa;
  font-size:clamp(.9rem,2.4vw,1rem);max-width:34ch;
}
.te2-replay{
  margin-top:.3rem;display:inline-flex;align-items:center;gap:.5rem;
  padding:.55rem 1.05rem;border-radius:999px;cursor:pointer;
  background:#16161a;color:#e9e9ee;
  border:1px solid #26262c;
  font-family:inherit;font-size:.85rem;font-weight:500;
  transition:border-color .2s ease,color .2s ease,transform .12s ease;
}
.te2-replay:hover{border-color:var(--akzent,#7c87ff);color:#f4f4f5;}
.te2-replay:active{transform:translateY(1px);}
.te2-replay:focus-visible{
  outline:2px solid var(--akzent,#7c87ff);outline-offset:2px;
}
.te2-replay svg{width:15px;height:15px;flex:0 0 auto;}
@media (prefers-reduced-motion:reduce){
  .te2-word{
    opacity:1;filter:blur(0);transform:none;
    transition:none;transition-delay:0ms;
  }
  .te2-replay{transition:none;}
}
</style>

<div class="te2-stage">
  <span class="te2-eyebrow">Willkommen</span>
  <h2 class="te2-headline" aria-label="Klarheit entsteht Wort für Wort.">Klarheit entsteht Wort für Wort.</h2>
  <p class="te2-sub">Jedes Wort schält sich sauber aus der Unschärfe.</p>
  <button type="button" class="te2-replay">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v4h4"/></svg>
    Erneut abspielen
  </button>
</div>

<script>
(function(){
  // Alle noch nicht verdrahteten Blur-Text-Buehnen finden (mehrfach-sicher).
  var stages = document.querySelectorAll('.te2-stage:not([data-te2-ready])');
  stages.forEach(function(stage){
    stage.setAttribute('data-te2-ready','1');
    var headline = stage.querySelector('.te2-headline');
    var replay   = stage.querySelector('.te2-replay');
    if(!headline) return;

    // Originaltext einmal sichern, dann in Wort-Spans zerlegen.
    var text = headline.getAttribute('aria-label') || headline.textContent;
    var words = text.split(/(\s+)/); // Leerraum als eigene Stuecke erhalten

    function build(){
      headline.textContent = '';
      var visIndex = 0; // nur echte Woerter bekommen eine Verzoegerung
      words.forEach(function(part){
        if(part.trim() === ''){
          headline.appendChild(document.createTextNode(part));
          return;
        }
        var span = document.createElement('span');
        span.className = 'te2-word';
        span.textContent = part;
        span.style.setProperty('--te2-delay', (visIndex * 90) + 'ms');
        headline.appendChild(span);
        visIndex++;
      });
    }

    function play(){
      build();
      // Reflow erzwingen, damit der Startzustand greift, dann einblenden.
      void headline.offsetWidth;
      requestAnimationFrame(function(){
        stage.querySelectorAll('.te2-word').forEach(function(w){
          w.classList.add('te2-in');
        });
      });
    }

    if(replay) replay.addEventListener('click', play);
    play();
  });
})();
</script>

Decrypted-Text (Entschlüsseln)

Pol 1 · Craft Anlass: Tech-/Hacker-Optik

Text entschlüsselt sich Buchstabe für Buchstabe von links: Zufalls-Glyphen rattern, jede Position fixiert nach kurzer Zeit auf das Zielzeichen. Vorbild: reactbits DecryptedText.

Zugriff wird entschlüsselt

Sicherer Kanal hergestellt.

Übertragung integer — alle Prüfsummen bestätigt.

<style>
.te3-stage{
  --te3-accent:var(--akzent,#7c87ff);
  display:flex;flex-direction:column;align-items:flex-start;gap:1.4rem;
  padding:2.4rem;border-radius:14px;
  background:linear-gradient(160deg,#1c1c22,#16161a);
  border:1px solid #26262c;
  font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  color:#e9e9ee;overflow:hidden;position:relative;
}
.te3-stage::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(60% 80% at 12% 0%,color-mix(in srgb,var(--te3-accent) 14%,transparent),transparent 70%);
  opacity:.7;
}
.te3-eyebrow{
  position:relative;z-index:1;
  font-family:ui-monospace,"SFMono-Regular",Menlo,Consolas,monospace;
  font-size:.72rem;letter-spacing:.22em;text-transform:uppercase;
  color:#a1a1aa;display:flex;align-items:center;gap:.55rem;
}
.te3-eyebrow::before{
  content:"";width:7px;height:7px;border-radius:50%;
  background:var(--te3-accent);
  box-shadow:0 0 10px color-mix(in srgb,var(--te3-accent) 80%,transparent);
}
.te3-headline{
  position:relative;z-index:1;margin:0;
  font-family:ui-monospace,"SFMono-Regular",Menlo,Consolas,monospace;
  font-size:clamp(1.5rem,4.4vw,2.4rem);line-height:1.18;font-weight:600;
  letter-spacing:.01em;max-width:22ch;text-wrap:balance;
}
.te3-char{display:inline-block;white-space:pre;transition:color .18s ease}
.te3-char.te3-scramble{
  color:var(--te3-accent);
  text-shadow:0 0 14px color-mix(in srgb,var(--te3-accent) 45%,transparent);
}
.te3-char.te3-locked{color:#f4f4f5}
.te3-sub{
  position:relative;z-index:1;margin:0;max-width:46ch;
  font-size:.95rem;line-height:1.55;color:#a1a1aa;min-height:1.55em;
}
.te3-replay{
  position:relative;z-index:1;margin-top:.3rem;
  display:inline-flex;align-items:center;gap:.55rem;
  padding:.62rem 1.15rem;border-radius:9px;cursor:pointer;
  background:#1c1c22;border:1px solid #26262c;color:#e9e9ee;
  font-family:ui-monospace,"SFMono-Regular",Menlo,Consolas,monospace;
  font-size:.82rem;letter-spacing:.04em;
  transition:transform .2s cubic-bezier(.22,1,.36,1),border-color .2s ease,background .2s ease;
}
.te3-replay:hover{
  border-color:color-mix(in srgb,var(--te3-accent) 55%,#26262c);
  background:#20202a;transform:translateY(-1px);
}
.te3-replay:active{transform:translateY(0)}
.te3-replay:focus-visible{
  outline:2px solid var(--te3-accent);outline-offset:3px;
}
.te3-replay svg{width:15px;height:15px;stroke:var(--te3-accent);
  transition:transform .3s cubic-bezier(.22,1,.36,1)}
.te3-replay:hover svg{transform:rotate(-90deg)}

@media (prefers-reduced-motion:reduce){
  .te3-char,.te3-replay,.te3-replay svg{transition:none}
  .te3-char.te3-scramble{color:#f4f4f5;text-shadow:none}
  .te3-replay:hover{transform:none}
  .te3-replay:hover svg{transform:none}
}
</style>

<div class="te3-stage">
  <span class="te3-eyebrow">Zugriff wird entschlüsselt</span>
  <h2 class="te3-headline" data-te3-text="Sicherer Kanal hergestellt.">Sicherer Kanal hergestellt.</h2>
  <p class="te3-sub" data-te3-text="Übertragung integer — alle Prüfsummen bestätigt.">Übertragung integer — alle Prüfsummen bestätigt.</p>
  <button class="te3-replay" type="button">
    <svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></svg>
    Erneut entschlüsseln
  </button>
</div>

<script>
(function(){
  var GLYPHS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!<>/\\{}[]#$%&*+=?";
  var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

  function rnd(){ return GLYPHS[Math.floor(Math.random()*GLYPHS.length)]; }

  // Ein Text-Element aufsetzen: Zielzeichen in Spans zerlegen
  function build(el){
    var text = el.getAttribute("data-te3-text") || el.textContent;
    el.setAttribute("data-te3-text", text);
    el.textContent = "";
    var spans = [];
    for (var i=0;i<text.length;i++){
      var s = document.createElement("span");
      s.className = "te3-char";
      s.textContent = text[i];
      el.appendChild(s);
      spans.push(s);
    }
    return { el: el, text: text, spans: spans, raf: 0 };
  }

  // Entschlüssel-Lauf: von links nach rechts fixieren, Rest scrambled
  function run(unit){
    if (unit.raf) cancelAnimationFrame(unit.raf);
    var text = unit.text, spans = unit.spans;

    if (reduce){
      for (var i=0;i<spans.length;i++){
        spans[i].textContent = text[i];
        spans[i].className = "te3-char";
      }
      return;
    }

    var start = null;
    var LOCK_STEP = 55;   // ms bis die naechste Position fixiert
    var SCRAMBLE_MS = 45; // Glyphen-Wechseltakt

    function frame(now){
      if (start === null) start = now;
      var elapsed = now - start;
      var locked = Math.floor(elapsed / LOCK_STEP); // Anzahl fixierter Positionen
      var tick = Math.floor(elapsed / SCRAMBLE_MS);
      var done = true;

      for (var i=0;i<spans.length;i++){
        var ch = text[i];
        if (i < locked || ch === " "){
          if (spans[i].textContent !== ch || spans[i].className !== "te3-char te3-locked"){
            spans[i].textContent = ch;
            spans[i].className = "te3-char te3-locked";
          }
        } else {
          done = false;
          spans[i].className = "te3-char te3-scramble";
          // pro Position leicht versetzter Glyphen-Wechsel
          if ((tick + i) % 2 === 0) spans[i].textContent = rnd();
        }
      }
      // "locked"-Glanz nach kurzer Zeit abklingen lassen
      for (var j=0;j<locked && j<spans.length;j++){
        spans[j].className = "te3-char";
      }

      if (done){ unit.raf = 0; return; }
      unit.raf = requestAnimationFrame(frame);
    }
    unit.raf = requestAnimationFrame(frame);
  }

  // Alle noch nicht verdrahteten Stages initialisieren (mehrfach-sicher)
  var stages = document.querySelectorAll(".te3-stage");
  for (var k=0;k<stages.length;k++){
    (function(stage){
      if (stage.dataset.te3Ready) return;
      stage.dataset.te3Ready = "1";

      var targets = stage.querySelectorAll(".te3-headline, .te3-sub");
      var units = [];
      for (var t=0;t<targets.length;t++) units.push(build(targets[t]));

      function playAll(){ for (var u=0;u<units.length;u++) run(units[u]); }

      var btn = stage.querySelector(".te3-replay");
      if (btn) btn.addEventListener("click", playAll);

      // Erststart bei Sichtbarkeit
      if ("IntersectionObserver" in window){
        var io = new IntersectionObserver(function(entries){
          entries.forEach(function(e){
            if (e.isIntersecting){ playAll(); io.disconnect(); }
          });
        }, { threshold: 0.35 });
        io.observe(stage);
      } else {
        playAll();
      }
    })(stages[k]);
  }
})();
</script>

Scrambled-Text (Hover-Würfeln)

Pol 1 · Craft Anlass: interaktive Headline

Bei Hover oder Fokus würfeln sich die Buchstaben kurz durcheinander und ordnen sich von links nach rechts wieder ein — ein taktiler Blickfang für Headlines. Vorbild: reactbits ScrambledText.

Zeiger drauf oder Tab

Handwerk mit Präzision

Mit Tab anspringen — startet den Effekt auch ohne Maus.

<style>
.te4-stage{
  display:flex; flex-direction:column; gap:1.4rem;
  align-items:flex-start; justify-content:center;
  min-height:220px; padding:2.4rem 2rem;
  background:radial-gradient(120% 140% at 12% 0%, #1c1c22 0%, #16161a 60%);
  border-radius:14px;
  font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
}
.te4-eyebrow{
  display:inline-flex; align-items:center; gap:.55rem;
  font-size:.72rem; letter-spacing:.16em; text-transform:uppercase;
  color:#a1a1aa; font-weight:600;
}
.te4-eyebrow::before{ content:""; width:22px; height:1px; background:var(--akzent,#7c87ff); }
.te4-headline{
  margin:0; padding:0; border:0; background:none;
  font-size:clamp(1.7rem,5.2vw,2.6rem); line-height:1.12;
  font-weight:700; letter-spacing:-.01em; color:#f4f4f5;
  text-align:left; cursor:pointer; font-family:inherit;
}
.te4-headline:focus-visible{ outline:2px solid var(--akzent,#7c87ff); outline-offset:6px; border-radius:6px; }
.te4-word{ display:inline-block; white-space:nowrap; }
.te4-ch{ display:inline-block; width:.62em; text-align:center; font-variant-numeric:tabular-nums; }
.te4-ch.is-live{ color:var(--akzent,#7c87ff); }
.te4-hint{ font-size:.82rem; color:#a1a1aa; margin:0; }
.te4-hint kbd{
  font:inherit; font-size:.74rem; color:#e9e9ee;
  background:#1c1c22; border:1px solid #26262c; border-radius:5px; padding:.05em .45em;
}
@media (prefers-reduced-motion: reduce){
  .te4-ch.is-live{ color:inherit; }
}
</style>
<article class="fx" id="te4-1">
  <div class="fx-meta">
    <h3>Scrambled-Text (Hover-Würfeln)</h3>
    <span class="fx-tag pol1">Pol 1 · Craft</span>
    <span class="fx-tag">Anlass: interaktive Headline</span>
  </div>
  <p class="fx-note">Bei Hover oder Fokus würfeln sich die Buchstaben kurz durcheinander und ordnen sich von links nach rechts wieder ein — ein taktiler Blickfang für Headlines. Vorbild: reactbits ScrambledText.</p>
  <div class="stage">
    <div class="te4-stage">
      <span class="te4-eyebrow">Zeiger drauf oder Tab</span>
      <h2 class="te4-headline" tabindex="0" data-text="Handwerk mit Präzision">Handwerk mit Präzision</h2>
      <p class="te4-hint">Mit <kbd>Tab</kbd> anspringen — startet den Effekt auch ohne Maus.</p>
    </div>
  </div>
</article>
<script>
(function(){
  var GLYPHS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!<>-_/[]{}=+*#";
  function rand(){ return GLYPHS.charAt(Math.floor(Math.random()*GLYPHS.length)); }
  var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  var stages = document.querySelectorAll(".te4-stage");
  Array.prototype.forEach.call(stages, function(stage){
    var el = stage.querySelector(".te4-headline");
    if(!el || el.dataset.te4Ready) return;
    el.dataset.te4Ready = "1";
    var text = el.getAttribute("data-text") || el.textContent;
    var cells = [];
    el.textContent = "";
    text.split(" ").forEach(function(word, wi, arr){
      var w = document.createElement("span");
      w.className = "te4-word";
      for(var i=0;i<word.length;i++){
        var c = document.createElement("span");
        c.className = "te4-ch";
        c.textContent = word.charAt(i);
        c.dataset.real = word.charAt(i);
        w.appendChild(c);
        cells.push(c);
      }
      el.appendChild(w);
      if(wi < arr.length-1) el.appendChild(document.createTextNode(" "));
    });
    var running = false;
    function play(){
      if(reduce || running) return;
      running = true;
      var start = null;
      var reveal = cells.map(function(_, i){ return 90 + i*22 + Math.random()*120; });
      var last = 0;
      function frame(ts){
        if(start === null) start = ts;
        var t = ts - start;
        var doScramble = (ts - last) > 32;
        if(doScramble) last = ts;
        var done = true;
        for(var i=0;i<cells.length;i++){
          var c = cells[i];
          if(t < reveal[i]){
            done = false;
            if(doScramble){ c.textContent = rand(); c.classList.add("is-live"); }
          } else if(c.classList.contains("is-live") || c.textContent !== c.dataset.real){
            c.textContent = c.dataset.real;
            c.classList.remove("is-live");
          }
        }
        if(done){ running = false; return; }
        requestAnimationFrame(frame);
      }
      requestAnimationFrame(frame);
    }
    el.addEventListener("mouseenter", play);
    el.addEventListener("focus", play);
  });
})();
</script>

Schreibmaschine (Typewriter)

Pol 2 · Klarheit Anlass: Hero-Subline

Tippt Phrasen Zeichen für Zeichen mit blinkendem Cursor und wechselt in Endlos-Schleife durch mehrere Aussagen — für lebendige Hero-Sublines. Vorbild: reactbits TextType.

Studio für Gestaltung Für Sie

<style>
.te5-stage{
  --akzent:#7c87ff;
  display:flex;align-items:center;justify-content:center;
  min-height:150px;padding:22px;
  background:radial-gradient(120% 140% at 50% 0%,#1c1c22 0%,#16161a 70%);
  border:1px solid #26262c;border-radius:12px;
}
.te5-line{
  font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  font-size:clamp(1.15rem,3.4vw,1.7rem);
  font-weight:600;line-height:1.35;color:#e9e9ee;
  text-align:center;letter-spacing:-.01em;max-width:22ch;margin:0;
}
.te5-eyebrow{
  display:block;font-size:.72rem;font-weight:600;
  letter-spacing:.22em;text-transform:uppercase;
  color:#a1a1aa;margin-bottom:.5rem;
}
.te5-word{color:var(--akzent,#7c87ff)}
.te5-caret{
  display:inline-block;width:.09em;height:1.05em;
  margin-left:.06em;vertical-align:-.14em;border-radius:2px;
  background:var(--akzent,#7c87ff);
  box-shadow:0 0 10px color-mix(in srgb,var(--akzent,#7c87ff) 55%,transparent);
  transform:translateZ(0);
}
/* Blinken nur im Ruhezustand (Halten/Pause), nicht beim aktiven Tippen */
.te5-caret.te5-blink{animation:te5-blink 1s steps(1,end) infinite}
@keyframes te5-blink{0%,50%{opacity:1}50.01%,100%{opacity:0}}

.te5-stage:focus-visible{outline:2px solid var(--akzent,#7c87ff);outline-offset:3px}

/* Reduzierte Bewegung: Endzustand (erste Phrase komplett), kein Tippen, kein Blinken */
@media (prefers-reduced-motion:reduce){
  .te5-caret.te5-blink{animation:none;opacity:1}
}
</style>

<div class="te5-stage" data-te5-typewriter tabindex="0" aria-label="Wechselnde Aussagen"
     data-phrases='["gestalten wir Räume, die bleiben.","bauen wir Marken mit Haltung.","lösen wir Probleme sauber."]'>
  <p class="te5-line" aria-live="polite">
    <span class="te5-eyebrow">Studio für Gestaltung</span>
    Für Sie <span class="te5-word"></span><span class="te5-caret te5-blink" aria-hidden="true"></span>
  </p>
</div>

<script>
(function(){
  var REDUCE = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  function init(stage){
    if(stage.dataset.te5Ready) return;          // Mehrfach-Init verhindern (mehrere Bausteine je Seite)
    stage.dataset.te5Ready = '1';

    var word  = stage.querySelector('.te5-word');
    var caret = stage.querySelector('.te5-caret');
    if(!word) return;

    var phrases;
    try { phrases = JSON.parse(stage.dataset.phrases || '[]'); } catch(e){ phrases = []; }
    if(!phrases.length) return;

    // Reduzierte Bewegung: erste Phrase komplett zeigen, kein Tippen, kein Blinken.
    if(REDUCE){
      word.textContent = phrases[0];
      if(caret) caret.classList.remove('te5-blink');
      return;
    }

    var TYPE = 60, DELETE = 32, HOLD = 1400, PAUSE = 420;   // Exits schneller als Enters
    var pi = 0, ci = 0, deleting = false;

    function tick(){
      var full = phrases[pi];
      if(!deleting){
        ci++;
        word.textContent = full.slice(0, ci);
        if(ci >= full.length){
          deleting = true;
          if(caret) caret.classList.add('te5-blink');       // Halten: Cursor blinkt
          setTimeout(tick, HOLD);
          return;
        }
        if(caret) caret.classList.remove('te5-blink');      // aktiv tippen: Cursor steht
        setTimeout(tick, TYPE);
      } else {
        ci--;
        word.textContent = full.slice(0, ci);
        if(caret) caret.classList.remove('te5-blink');
        if(ci <= 0){
          deleting = false;
          pi = (pi + 1) % phrases.length;                   // nächste Phrase, Endlos-Schleife
          if(caret) caret.classList.add('te5-blink');       // kurze Pause vor der nächsten Phrase
          setTimeout(tick, PAUSE);
          return;
        }
        setTimeout(tick, DELETE);
      }
    }
    setTimeout(tick, 500);
  }

  function boot(){
    var nodes = document.querySelectorAll('[data-te5-typewriter]');
    for(var i=0;i<nodes.length;i++) init(nodes[i]);
  }
  if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
  else boot();
})();
</script>

Rotierendes Wort

Pol 1 · Craft Anlass: dynamische Aussage

In einem festen Satz wechselt ein Wort zyklisch durch eine Liste: das alte Wort gleitet vertikal aus dem Maskenfenster, das neue rein — nur transform, endlos, bei reduzierter Bewegung statisch. Vorbild: reactbits RotatingText.

Wir bauen schnell sauber zuverlässig termintreu

<p class="te6-rotate">
  Wir bauen
  <span class="te6-slot" aria-live="polite">
    <span class="te6-slot-list">
      <span class="te6-slot-item">schnell</span>
      <span class="te6-slot-item">sauber</span>
      <span class="te6-slot-item">zuverlässig</span>
      <span class="te6-slot-item">termintreu</span>
    </span>
  </span>
  <span aria-hidden="true">.</span>
</p>

<style>
.te6-rotate{display:inline-flex;align-items:baseline;flex-wrap:wrap;gap:.3ch;
  font-size:clamp(1.15rem,4.5vw,1.7rem);font-weight:600;color:#e9e9ee;letter-spacing:.01em}
/* Maskenfenster: nur eine Zeilenhoehe hoch, Rest wird abgeschnitten */
.te6-slot{position:relative;display:inline-block;overflow:hidden;
  height:1.35em;vertical-align:bottom;
  color:var(--akzent,#7c87ff);font-weight:700}
.te6-slot-list{display:block;position:relative;
  transition:transform .5s cubic-bezier(.22,1,.36,1)}
.te6-slot-item{display:block;height:1.35em;line-height:1.35em;white-space:nowrap}
.te6-slot::before,.te6-slot::after{content:"";position:absolute;left:0;right:0;height:.35em;
  pointer-events:none;z-index:1}
.te6-slot::before{top:0;background:linear-gradient(#16161a,transparent)}
.te6-slot::after{bottom:0;background:linear-gradient(transparent,#16161a)}
@media (prefers-reduced-motion:reduce){
  .te6-slot-list{transition:none}
}
</style>

<script>
/* Mehrfach-sicher: jede .te6-slot-Instanz bekommt einen eigenen Zyklus */
document.querySelectorAll('.te6-slot').forEach(function(slot){
  var list = slot.querySelector('.te6-slot-list');
  if(!list) return;
  var items = Array.prototype.slice.call(list.children);
  if(items.length < 2) return;

  /* Klon des ersten Wortes ans Ende — fuer den nahtlosen Ruecksprung */
  var clone = items[0].cloneNode(true);
  clone.setAttribute('aria-hidden','true');
  list.appendChild(clone);

  var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion:reduce)').matches;
  if(reduce) return; /* statischer Endzustand: erstes Wort bleibt stehen */

  var i = 0, step = items[0].offsetHeight, n = items.length;

  function advance(){
    i++;
    list.style.transition = 'transform .5s cubic-bezier(.22,1,.36,1)';
    list.style.transform = 'translateY(' + (-i * step) + 'px)';
    /* am Klon angekommen: nach dem Uebergang ohne Animation auf 0 zuruecksetzen */
    if(i === n){
      window.setTimeout(function(){
        list.style.transition = 'none';
        list.style.transform = 'translateY(0)';
        i = 0;
      }, 520);
    }
  }
  window.setInterval(advance, 2200);
});
</script>

Shiny-Text (Glanz-Sweep)

Pol 1 · Craft Anlass: edler Headline-Akzent

Ein heller Streifen wandert in Endlos-Schleife dezent über gedämpften Text (animierter linear-gradient per background-clip:text), für einen edlen Headline-Akzent — reines CSS, bei reduzierter Bewegung als voll ausgeleuchteter Endzustand. Vorbild: reactbits ShinyText.

Handwerk, das hält.

Qualität für Ihr Zuhause

Endlos-Schleife · dezenter Glanz-Sweep über gedämpftem Text

<div class="te7-stage">
  <p class="te7-shine te7-h1">Handwerk, das hält.</p>
  <p class="te7-shine te7-h2">Qualität für Ihr Zuhause</p>
  <p class="te7-hint">Endlos-Schleife · dezenter Glanz-Sweep über gedämpftem Text</p>
</div>

<style>
/* --- Shiny-Text (Glanz-Sweep) — reines CSS, kein JS noetig --- */
.te7-stage{display:grid;place-items:center;gap:1.5rem;text-align:center;width:100%;
  padding:2.4rem 1.2rem}
.te7-shine{position:relative;display:inline-block;margin:0;font-weight:700;line-height:1.12;
  letter-spacing:-.01em;
  /* gedaempfte Grundfarbe der Glyphen */
  color:#5a5a66;
  /* Glanz-Band: transparent -> hell -> transparent, wandert via background-position */
  background-image:linear-gradient(100deg,
    transparent 20%,#f4f4f5 42%,#ffffff 50%,#f4f4f5 58%,transparent 80%);
  background-size:220% 100%;
  background-position:120% 0;
  background-repeat:no-repeat;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;
  animation:te7Sweep 3.6s linear infinite}
.te7-shine.te7-h1{font-size:clamp(1.9rem,7vw,3rem)}
.te7-shine.te7-h2{font-size:clamp(1.2rem,4.4vw,1.7rem);font-weight:600;color:#48484f;
  /* Akzent-Variante desselben Effekts (immer var(--akzent)) */
  background-image:linear-gradient(100deg,
    transparent 22%,var(--akzent,#7c87ff) 46%,#c7ccff 50%,var(--akzent,#7c87ff) 54%,transparent 78%);
  animation-duration:4.4s}
.te7-hint{font-size:.8rem;color:#a1a1aa;letter-spacing:.02em;margin:.2rem 0 0}

/* Band von rechts (120%) nach links (-40%) ueber die Glyphen ziehen, dann kurze Pause */
@keyframes te7Sweep{
  0%{background-position:120% 0}
  60%,100%{background-position:-40% 0}
}

/* Motion aus: kein Lauf, Endzustand = einmal durchgezogener heller Text */
@media (prefers-reduced-motion:reduce){
  .te7-shine{animation:none;background-position:50% 0;color:#f4f4f5}
  .te7-shine.te7-h2{color:#c7ccff}
}
</style>

Verlaufs-Text (animiert)

Pol 1 · Craft Anlass: Marken-Headline

Ein animierter Farbverlauf fliesst endlos und weich durch die Headline (background-clip:text) und bindet die Akzentfarbe ein. Vorbild: reactbits GradientText.

Ideen, die leuchten.

<style>
/* Verlaufs-Text (animiert) — alles scoped ueber .te8- */
.te8-stage{
  display:flex;
  align-items:center;
  justify-content:center;
  min-height:220px;
  padding:48px 28px;
  background:
    radial-gradient(120% 140% at 50% 0%, #1c1c22 0%, #16161a 70%);
  border-radius:14px;
}
.te8-headline{
  margin:0;
  font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  font-weight:800;
  font-size:clamp(2rem,7vw,4rem);
  line-height:1.05;
  letter-spacing:-0.02em;
  text-align:center;
  text-wrap:balance;
  /* Verlauf: gedaempft -> hell -> Akzent -> hell -> gedaempft, breit gezogen */
  background-image:linear-gradient(
    100deg,
    #a1a1aa 0%,
    #f4f4f5 22%,
    var(--akzent,#7c87ff) 42%,
    #f4f4f5 62%,
    var(--akzent,#7c87ff) 78%,
    #a1a1aa 100%
  );
  background-size:220% 100%;
  background-position:0% 50%;
  /* Verlauf in die Buchstaben clippen */
  -webkit-background-clip:text;
  background-clip:text;
  -webkit-text-fill-color:transparent;
  color:transparent;
  animation:te8-flow 5.5s linear infinite;
}
.te8-headline .te8-em{ font-style:normal; }

@keyframes te8-flow{
  to{ background-position:-220% 50%; }
}

/* A11y: Motion neutralisieren, Endzustand als ruhiger Verlauf zeigen */
@media (prefers-reduced-motion: reduce){
  .te8-headline{
    animation:none;
    background-position:50% 50%;
  }
}
</style>

<h2 class="te8-headline">Ideen, die <em class="te8-em">leuchten</em>.</h2>

True-Focus (Wort-Fokus)

Pol 1 · Craft Anlass: Aufmerksamkeit lenken

Woerter stehen unscharf und gedaempft, nur das fokussierte ist scharf und bekommt einen wandernden Eckklammer-Rahmen — lenkt den Blick gezielt durch eine Zeile. Vorbild: reactbits TrueFocus.

Praezise fokussiert scharf gestellt
<style>
.te9-wrap{display:flex;flex-direction:column;align-items:center}
.te9-stage{
  display:flex;
  align-items:center;
  justify-content:center;
  min-height:200px;
  width:100%;
}
.te9-focus{
  position:relative;
  display:flex;
  flex-wrap:wrap;
  gap:.5em 1.1em;
  align-items:center;
  justify-content:center;
  max-width:14em;
  font-size:clamp(1.9rem,7vw,3.4rem);
  font-weight:700;
  letter-spacing:-.01em;
  line-height:1.15;
  color:#f4f4f5;
  --te9-blur:5px;
  --te9-dim:.42;
}
.te9-word{
  position:relative;
  display:inline-block;
  padding:.04em .12em;
  color:inherit;
  filter:blur(var(--te9-blur));
  opacity:var(--te9-dim);
  transition:filter .5s cubic-bezier(.22,.61,.36,1),opacity .5s cubic-bezier(.22,.61,.36,1);
  will-change:filter,opacity;
}
.te9-word.is-active{filter:blur(0);opacity:1}
/* Ein einzelner Rahmen, der per transform ueber das aktive Wort gleitet */
.te9-frame{
  position:absolute;
  top:0;
  left:0;
  pointer-events:none;
  opacity:0;
  transform:translate(0,0);
  transition:transform .42s cubic-bezier(.22,.61,.36,1),
             width .42s cubic-bezier(.22,.61,.36,1),
             height .42s cubic-bezier(.22,.61,.36,1),
             opacity .28s ease;
  will-change:transform,width,height;
}
.te9-frame.is-on{opacity:1}
.te9-frame.no-anim{transition:none}
.te9-corner{
  position:absolute;
  width:.5em;
  height:.5em;
  border:2px solid var(--akzent,#7c87ff);
  filter:drop-shadow(0 0 6px color-mix(in srgb,var(--akzent,#7c87ff) 55%,transparent));
}
.te9-corner.tl{top:-.16em;left:-.16em;border-right:none;border-bottom:none;border-top-left-radius:4px}
.te9-corner.tr{top:-.16em;right:-.16em;border-left:none;border-bottom:none;border-top-right-radius:4px}
.te9-corner.bl{bottom:-.16em;left:-.16em;border-right:none;border-top:none;border-bottom-left-radius:4px}
.te9-corner.br{bottom:-.16em;right:-.16em;border-left:none;border-top:none;border-bottom-right-radius:4px}

.te9-replay{
  margin-top:28px;
  display:inline-flex;
  align-items:center;
  gap:.5em;
  font:inherit;
  font-size:.82rem;
  color:#e9e9ee;
  background:#16161a;
  border:1px solid #26262c;
  border-radius:999px;
  padding:.5em 1.05em;
  cursor:pointer;
  transition:border-color .2s ease,transform .12s ease;
}
.te9-replay:hover{border-color:var(--akzent,#7c87ff)}
.te9-replay:focus-visible{outline:2px solid var(--akzent,#7c87ff);outline-offset:3px}
.te9-replay:active{transform:translateY(1px)}

@media (prefers-reduced-motion:reduce){
  .te9-word,.te9-frame,.te9-replay{transition:none}
}
</style>
<div class="te9-wrap">
  <div class="te9-stage">
    <div class="te9-focus" role="text" aria-label="Praezise fokussiert scharf gestellt">
      <span class="te9-word">Praezise</span>
      <span class="te9-word">fokussiert</span>
      <span class="te9-word">scharf</span>
      <span class="te9-word">gestellt</span>
      <span class="te9-frame" aria-hidden="true">
        <span class="te9-corner tl"></span>
        <span class="te9-corner tr"></span>
        <span class="te9-corner bl"></span>
        <span class="te9-corner br"></span>
      </span>
    </div>
  </div>
  <button type="button" class="te9-replay">Fokus neu starten</button>
</div>

<script>
(function(){
  var stages=document.querySelectorAll('.te9-stage');
  for(var i=0;i<stages.length;i++){wire(stages[i]);}

  function wire(stage){
    if(stage.dataset.te9Wired)return;
    stage.dataset.te9Wired='1';
    var focus=stage.querySelector('.te9-focus');
    var words=stage.querySelectorAll('.te9-word');
    var frame=stage.querySelector('.te9-frame');
    if(!focus||!words.length||!frame)return;

    var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    var idx=0,timer=null,first=true;

    function place(el){
      var fb=focus.getBoundingClientRect();
      var wb=el.getBoundingClientRect();
      frame.style.width=wb.width+'px';
      frame.style.height=wb.height+'px';
      frame.style.transform='translate('+(wb.left-fb.left)+'px,'+(wb.top-fb.top)+'px)';
    }
    function activate(n){
      for(var k=0;k<words.length;k++){words[k].classList.toggle('is-active',k===n);}
      if(first){
        // Erste Platzierung ohne Wander-Animation, damit der Rahmen
        // nicht aus der Ecke einfliegt.
        frame.classList.add('no-anim');
        place(words[n]);
        frame.classList.add('is-on');
        void frame.offsetWidth;            // Reflow erzwingen
        frame.classList.remove('no-anim');
        first=false;
      }else{
        place(words[n]);
        frame.classList.add('is-on');
      }
    }
    function step(){idx=(idx+1)%words.length;activate(idx);}
    function start(){
      if(timer)clearInterval(timer);
      idx=0;first=true;activate(0);
      if(!reduce)timer=setInterval(step,1400);
    }

    start(); // bei reduce: nur Wort 0 scharf + Rahmen sichtbar, kein Intervall

    var reposition=function(){place(words[idx]);};
    if(window.ResizeObserver){new ResizeObserver(reposition).observe(focus);}
    window.addEventListener('resize',reposition);

    var btn=stage.parentNode.querySelector('.te9-replay');
    if(btn){btn.addEventListener('click',start);}
  }
})();
</script>

Glitch-Text (RGB-Split)

Pol 1 · Craft Anlass: Tech/Gaming-Optik

Zwei farbig versetzte Textkopien (Cyan/Magenta) erzeugen per clip-path-Spruengen einen sparsamen RGB-Split-Glitch fuer Tech- und Gaming-Ueberschriften. Vorbild: reactbits GlitchText.

SYSTEM_ONLINE
<style>
/* Glitch-Text (RGB-Split) — alles scoped ueber te10- */
.te10-stage{
  --akzent:#7c87ff;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1.4rem;
  min-height:220px;padding:2.4rem 1.5rem;
  background:#16161a;border:1px solid #26262c;border-radius:14px;
  font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
}
.te10-glitch{
  position:relative;
  font-size:clamp(2rem,7vw,3.4rem);font-weight:800;letter-spacing:.02em;
  color:#f4f4f5;line-height:1;text-transform:uppercase;cursor:default;
}
/* zwei farbig versetzte Kopien des Textes */
.te10-glitch::before,
.te10-glitch::after{content:attr(data-text);position:absolute;inset:0;pointer-events:none}
.te10-glitch::before{
  color:#00e5ff;transform:translate3d(-2px,0,0);mix-blend-mode:screen;
  animation:te10-shift-a 5.4s steps(1) infinite;
}
.te10-glitch::after{
  color:#ff2fb9;transform:translate3d(2px,0,0);mix-blend-mode:screen;
  animation:te10-shift-b 5.4s steps(1) infinite;
}
/* sparsame Glitch-Bursts per clip-path-Spruengen, dazwischen Ruhe */
@keyframes te10-shift-a{
  0%,7%   {clip-path:inset(0 0 100% 0);transform:translate3d(-2px,0,0)}
  8%      {clip-path:inset(12% 0 62% 0);transform:translate3d(-6px,1px,0)}
  9%      {clip-path:inset(70% 0 8% 0); transform:translate3d(5px,-1px,0)}
  10%     {clip-path:inset(30% 0 45% 0);transform:translate3d(-4px,0,0)}
  11%,12% {clip-path:inset(0 0 100% 0); transform:translate3d(-2px,0,0)}
  40%     {clip-path:inset(0 0 100% 0); transform:translate3d(-2px,0,0)}
  41%     {clip-path:inset(48% 0 30% 0);transform:translate3d(4px,1px,0)}
  42%     {clip-path:inset(0 0 100% 0); transform:translate3d(-2px,0,0)}
  100%    {clip-path:inset(0 0 100% 0); transform:translate3d(-2px,0,0)}
}
@keyframes te10-shift-b{
  0%,7%   {clip-path:inset(0 0 100% 0);transform:translate3d(2px,0,0)}
  8%      {clip-path:inset(58% 0 20% 0);transform:translate3d(5px,-1px,0)}
  9%      {clip-path:inset(20% 0 60% 0);transform:translate3d(-5px,1px,0)}
  10%     {clip-path:inset(75% 0 4% 0); transform:translate3d(4px,0,0)}
  11%,12% {clip-path:inset(0 0 100% 0); transform:translate3d(2px,0,0)}
  41%     {clip-path:inset(22% 0 55% 0);transform:translate3d(-4px,-1px,0)}
  42%     {clip-path:inset(0 0 100% 0); transform:translate3d(2px,0,0)}
  100%    {clip-path:inset(0 0 100% 0); transform:translate3d(2px,0,0)}
}
/* bei Hover/Fokus intensiver (schnellere Wiederholung) */
.te10-glitch:hover::before,
.te10-glitch:focus-visible::before,
.te10-glitch:hover::after,
.te10-glitch:focus-visible::after{animation-duration:1.6s}
.te10-glitch:focus-visible{outline:2px solid var(--akzent,#7c87ff);outline-offset:6px;border-radius:4px}
/* einmaliger Burst per Button */
.te10-glitch.te10-burst::before{animation:te10-shift-a 1.2s steps(1) 1}
.te10-glitch.te10-burst::after{animation:te10-shift-b 1.2s steps(1) 1}

.te10-replay{
  font:inherit;font-size:.85rem;font-weight:600;color:#e9e9ee;
  background:#1c1c22;border:1px solid #26262c;border-radius:8px;
  padding:.5rem .9rem;cursor:pointer;
  transition:border-color .2s ease,transform .2s ease;
}
.te10-replay:hover{border-color:var(--akzent,#7c87ff)}
.te10-replay:active{transform:translateY(1px)}
.te10-replay:focus-visible{outline:2px solid var(--akzent,#7c87ff);outline-offset:3px}

/* Motion aus: statischer RGB-Split-Endzustand, kein Flackern */
@media (prefers-reduced-motion: reduce){
  .te10-glitch::before,
  .te10-glitch::after,
  .te10-glitch.te10-burst::before,
  .te10-glitch.te10-burst::after{animation:none}
  .te10-glitch::before{transform:translate3d(-2px,0,0);clip-path:inset(0 0 66% 0)}
  .te10-glitch::after{transform:translate3d(2px,0,0);clip-path:inset(66% 0 0 0)}
  .te10-replay{transition:none}
}
</style>

<div class="te10-stage">
  <span class="te10-glitch" data-text="SYSTEM_ONLINE" tabindex="0">SYSTEM_ONLINE</span>
  <button type="button" class="te10-replay">Glitch ausloesen</button>
</div>

<script>
(function(){
  var stages=document.querySelectorAll('.te10-stage');
  stages.forEach(function(stage){
    if(stage.dataset.te10Wired)return;      // mehrfach-sicher
    stage.dataset.te10Wired='1';
    var glitch=stage.querySelector('.te10-glitch');
    var btn=stage.querySelector('.te10-replay');
    if(!glitch||!btn)return;
    btn.addEventListener('click',function(){
      glitch.classList.remove('te10-burst'); // Reflow erzwingen, damit die Animation neu startet
      void glitch.offsetWidth;
      glitch.classList.add('te10-burst');
    });
    glitch.addEventListener('animationend',function(){
      glitch.classList.remove('te10-burst');
    });
  });
})();
</script>

Fuzzy-Text (Rauschen)

Pol 1 · Craft Anlass: rohe/kantige Optik

Überschrift mit rauem Fuzz aus versetzten Schatten-Ebenen und Sub-Pixel-Jitter — für kantige, laute Hero-Wörter. Vorbild: reactbits FuzzyText.

Störsignal

<style>
.te11-stage{
  --te11-jit: .6px;
  position:relative;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:22px;
  min-height:230px; padding:34px 22px;
  background:#16161a; border:1px solid #26262c; border-radius:14px;
  overflow:hidden;
}
.te11-stage::before{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:radial-gradient(120% 80% at 50% 0%, color-mix(in srgb, var(--akzent,#7c87ff) 12%, transparent), transparent 60%);
}
.te11-fuzz{
  position:relative; margin:0;
  font-size:clamp(2.6rem, 9vw, 4.4rem); font-weight:800; letter-spacing:.01em;
  line-height:1; color:#f4f4f5;
  text-shadow:
     .55px 0 0 var(--akzent,#7c87ff),
    -.55px 0 0 color-mix(in srgb, var(--akzent,#7c87ff) 55%, transparent),
     0 .6px 0 rgba(233,233,238,.32),
     0 -.6px 0 rgba(233,233,238,.20),
     .9px .3px .4px rgba(0,0,0,.55),
    -.7px -.4px .4px rgba(0,0,0,.40);
  animation:te11-jitter 90ms steps(2,end) infinite;
  will-change:transform, text-shadow;
}
.te11-fuzz .te11-ac{ color:var(--akzent,#7c87ff); }

@keyframes te11-jitter{
  0%   { transform:translate(0,0); }
  25%  { transform:translate(var(--te11-jit), calc(var(--te11-jit) * -.6)); }
  50%  { transform:translate(calc(var(--te11-jit) * -.7), var(--te11-jit)); }
  75%  { transform:translate(calc(var(--te11-jit) * .4), calc(var(--te11-jit) * .5)); }
  100% { transform:translate(0,0); }
}

.te11-controls{ display:flex; gap:10px; flex-wrap:wrap; justify-content:center; }
.te11-btn{
  font:inherit; font-size:.82rem; color:#e9e9ee;
  background:#1c1c22; border:1px solid #26262c; border-radius:9px;
  padding:8px 15px; cursor:pointer;
  transition:border-color .18s ease, background-color .18s ease, transform .12s ease;
}
.te11-btn:hover{ border-color:var(--akzent,#7c87ff); background:#22222a; }
.te11-btn:active{ transform:translateY(1px); }
.te11-btn:focus-visible{ outline:2px solid var(--akzent,#7c87ff); outline-offset:2px; }
.te11-btn[aria-pressed="true"]{
  border-color:var(--akzent,#7c87ff);
  box-shadow:inset 0 0 0 1px var(--akzent,#7c87ff);
}

/* Fuzz-Staerken ueber Datenattribut am Wurzel-Element */
.te11-stage[data-fuzz="off"]  .te11-fuzz{ animation:none; text-shadow:.55px 0 0 var(--akzent,#7c87ff), -.55px 0 0 rgba(233,233,238,.20), 0 1px 0 rgba(0,0,0,.45); }
.te11-stage[data-fuzz="soft"] { --te11-jit:.4px; }
.te11-stage[data-fuzz="soft"] .te11-fuzz{ animation-duration:120ms; }
.te11-stage[data-fuzz="rough"]{ --te11-jit:.85px; }
.te11-stage[data-fuzz="rough"] .te11-fuzz{ animation-duration:70ms; }

@media (prefers-reduced-motion: reduce){
  .te11-fuzz{ animation:none !important; transform:none !important; }
}
</style>

<div class="te11-stage" data-fuzz="soft">
  <p class="te11-fuzz">St&ouml;r<span class="te11-ac">signal</span></p>
  <div class="te11-controls">
    <button type="button" class="te11-btn" data-set="soft" aria-pressed="true">Weich</button>
    <button type="button" class="te11-btn" data-set="rough" aria-pressed="false">Rau</button>
    <button type="button" class="te11-btn" data-set="off" aria-pressed="false">Ruhig</button>
  </div>
</div>

<script>
(function(){
  var stages = document.querySelectorAll('.te11-stage');
  Array.prototype.forEach.call(stages, function(stage){
    if (stage.dataset.te11Ready) return;
    stage.dataset.te11Ready = "1";
    var btns = stage.querySelectorAll('.te11-btn');
    Array.prototype.forEach.call(btns, function(btn){
      btn.addEventListener('click', function(){
        stage.setAttribute('data-fuzz', btn.getAttribute('data-set'));
        Array.prototype.forEach.call(btns, function(b){
          b.setAttribute('aria-pressed', b === btn ? 'true' : 'false');
        });
      });
    });
  });
})();
</script>

Kreis-Text (rotierend)

Pol 1 · Craft Anlass: Badge/Siegel

Ein kurzer Text kreisfoermig um eine Scheibe angeordnet (jeder Buchstabe per rotate + translateY auf dem Kreis), die ganze Scheibe rotiert endlos langsam — als „Seit 2004 · Meisterbetrieb"-Siegel. Vorbild: reactbits CircularText.

<div class="te12-stage">
  <div class="te12-badge" tabindex="0" role="img"
       aria-label="Siegel: Seit 2004 · Meisterbetrieb · Handwerk mit Herz">
    <div class="te12-ring" data-te12-text="SEIT 2004 · MEISTERBETRIEB · HANDWERK MIT HERZ · " aria-hidden="true"></div>
    <div class="te12-hub" aria-hidden="true">
      <svg class="te12-hub-mark" viewBox="0 0 24 24" fill="none" stroke="currentColor"
           stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
        <path d="M12 3l2.4 5 5.6.6-4.2 3.7 1.3 5.7L12 18l-5.1 3 1.3-5.7L4 11.6l5.6-.6z"/>
      </svg>
      <div class="te12-hub-year">EST. 2004</div>
      <div class="te12-hub-sub">Meister</div>
    </div>
  </div>
</div>

<style>
.te12-stage{display:grid;place-items:center;min-height:300px;padding:28px}
.te12-badge{position:relative;width:200px;height:200px;color:#e9e9ee}
.te12-ring{position:absolute;inset:0;animation:te12-spin 24s linear infinite}
.te12-char{position:absolute;left:50%;top:50%;font-size:13px;font-weight:600;
  letter-spacing:.02em;color:#f4f4f5;transform-origin:0 0;will-change:transform}
.te12-ring::before{content:"";position:absolute;inset:8px;border-radius:50%;border:1px solid #26262c}
.te12-ring::after{content:"";position:absolute;inset:22px;border-radius:50%;border:1px solid #26262c}
.te12-hub{position:absolute;inset:38px;border-radius:50%;display:grid;place-content:center;gap:4px;
  text-align:center;background:radial-gradient(circle at 50% 36%,#1c1c22,#16161a);
  border:1px solid #26262c;box-shadow:inset 0 0 0 1px rgba(124,135,255,.08)}
.te12-hub-mark{width:34px;height:34px;margin:0 auto;color:var(--akzent,#7c87ff)}
.te12-hub-year{font-size:11px;font-weight:700;letter-spacing:.14em;color:var(--akzent,#7c87ff)}
.te12-hub-sub{font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:#a1a1aa}
.te12-badge:focus-visible{outline:2px solid var(--akzent,#7c87ff);outline-offset:6px;border-radius:50%}
@keyframes te12-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.te12-ring{animation:none}}
</style>

<script>
(function(){
  /* Buchstaben-Spans erzeugen und je per rotate() + translateY() auf den Kreis setzen.
     Mehrfach-sicher (laeuft ueber alle .te12-stage) und idempotent (dataset-Waechter). */
  document.querySelectorAll('.te12-stage').forEach(function(stage){
    var ring = stage.querySelector('[data-te12-text]');
    if(!ring || ring.dataset.te12Done) return;
    ring.dataset.te12Done = '1';
    var text = ring.getAttribute('data-te12-text') || '';
    var chars = Array.from(text);
    var step = 360 / chars.length;
    var radius = 82;               /* Radius in px = Abstand vom Mittelpunkt */
    chars.forEach(function(ch, i){
      var span = document.createElement('span');
      span.className = 'te12-char';
      span.textContent = ch === ' ' ? ' ' : ch;
      /* rotate positioniert den Winkel, translateY schiebt nach aussen, translateX zentriert das Glyph */
      span.style.transform =
        'rotate(' + (i * step) + 'deg) translateY(-' + radius + 'px) translateX(-50%)';
      ring.appendChild(span);
    });
  });
})();
</script>

Lauftext-Band (Marquee)

Pol 1 · Craft Anlass: Ticker/Lauftext

Endlos horizontal laufendes Groß-Typo-Band: zwei identische Läufe hintereinander, per requestAnimationFrame nahtlos verschoben, Trenn-Symbole zwischen den Phrasen, weiche Rand-Fades über mask-image, Pause bei Hover/Fokus. Vorbild: reactbits CurvedLoop.

Qualität Handwerk Präzision Verlässlich
<div class="te13-band" tabindex="0" role="marquee" aria-label="Lauftext-Band">
  <div class="te13-track">
    <div class="te13-run">
      <span class="te13-phrase">Qualität</span>
      <span class="te13-sep" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.6 6.6L21 9.2l-4.8 4.3L17.6 21 12 17.3 6.4 21l1.4-7.5L3 9.2l6.4-.6z"/></svg></span>
      <span class="te13-phrase te13-out">Handwerk</span>
      <span class="te13-sep" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.6 6.6L21 9.2l-4.8 4.3L17.6 21 12 17.3 6.4 21l1.4-7.5L3 9.2l6.4-.6z"/></svg></span>
      <span class="te13-phrase">Präzision</span>
      <span class="te13-sep" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.6 6.6L21 9.2l-4.8 4.3L17.6 21 12 17.3 6.4 21l1.4-7.5L3 9.2l6.4-.6z"/></svg></span>
      <span class="te13-phrase te13-out">Verlässlich</span>
      <span class="te13-sep" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.6 6.6L21 9.2l-4.8 4.3L17.6 21 12 17.3 6.4 21l1.4-7.5L3 9.2l6.4-.6z"/></svg></span>
    </div>
    <!-- identischer Lauf erneut, aria-hidden, fuer den nahtlosen Loop -->
    <div class="te13-run" aria-hidden="true">
      <span class="te13-phrase">Qualität</span>
      <span class="te13-sep"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.6 6.6L21 9.2l-4.8 4.3L17.6 21 12 17.3 6.4 21l1.4-7.5L3 9.2l6.4-.6z"/></svg></span>
      <span class="te13-phrase te13-out">Handwerk</span>
      <span class="te13-sep"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.6 6.6L21 9.2l-4.8 4.3L17.6 21 12 17.3 6.4 21l1.4-7.5L3 9.2l6.4-.6z"/></svg></span>
      <span class="te13-phrase">Präzision</span>
      <span class="te13-sep"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.6 6.6L21 9.2l-4.8 4.3L17.6 21 12 17.3 6.4 21l1.4-7.5L3 9.2l6.4-.6z"/></svg></span>
      <span class="te13-phrase te13-out">Verlässlich</span>
      <span class="te13-sep"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.6 6.6L21 9.2l-4.8 4.3L17.6 21 12 17.3 6.4 21l1.4-7.5L3 9.2l6.4-.6z"/></svg></span>
    </div>
  </div>
</div>

<style>
.te13-band{position:relative;width:100%;overflow:hidden;padding:1.4rem 0;
  background:linear-gradient(180deg,#1c1c22,#16161a);border-top:1px solid #26262c;border-bottom:1px solid #26262c;
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 12%,#000 88%,transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 12%,#000 88%,transparent 100%)}
.te13-track{display:flex;width:max-content;will-change:transform}
.te13-run{display:flex;align-items:center;flex:0 0 auto}
.te13-phrase{flex:0 0 auto;display:inline-flex;align-items:center;padding:0 .55em;
  font-size:clamp(1.5rem,4.5vw,2.6rem);font-weight:700;letter-spacing:-.01em;line-height:1;
  color:#f4f4f5;white-space:nowrap;text-transform:uppercase}
.te13-phrase.te13-out{color:transparent;-webkit-text-stroke:1px var(--akzent,#7c87ff);text-stroke:1px var(--akzent,#7c87ff);
  -webkit-text-fill-color:transparent}
.te13-sep{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:1.8em;height:1.8em;color:var(--akzent,#7c87ff);opacity:.85}
.te13-sep svg{width:.7em;height:.7em;display:block}
.te13-band:focus-visible{outline:2px solid var(--akzent,#7c87ff);outline-offset:3px}
@media (prefers-reduced-motion:reduce){
  .te13-band{-webkit-mask-image:none;mask-image:none}
  .te13-track{transform:none!important;flex-wrap:wrap;justify-content:center;gap:.2em .6em;width:100%}
  .te13-run:nth-child(2){display:none}
}
</style>

<script>
(function(){
  var bands = document.querySelectorAll('.te13-band');
  bands.forEach(function(band){
    if (band.dataset.te13Init) return;      // mehrfach-sicher
    band.dataset.te13Init = '1';
    var track = band.querySelector('.te13-track');
    var firstRun = band.querySelector('.te13-run');
    if (!track || !firstRun) return;
    var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
    var speed = 60;                          // Pixel pro Sekunde
    var offset = 0, half = 0, paused = false, last = 0, raf = 0;
    function measure(){ half = firstRun.getBoundingClientRect().width; }
    function frame(t){
      if (!last) last = t;
      var dt = (t - last) / 1000; last = t;
      if (!paused && half > 0){
        offset -= speed * dt;
        if (offset <= -half) offset += half; // nahtloser Ruecksprung
        track.style.transform = 'translate3d(' + offset + 'px,0,0)';
      }
      raf = requestAnimationFrame(frame);
    }
    function start(){ if (raf) return; last = 0; raf = requestAnimationFrame(frame); }
    function stop(){ if (raf) cancelAnimationFrame(raf); raf = 0; track.style.transform = ''; }
    function pause(){ paused = true; }
    function resume(){ last = 0; paused = false; }
    band.addEventListener('mouseenter', pause);
    band.addEventListener('mouseleave', resume);
    band.addEventListener('focusin', pause);
    band.addEventListener('focusout', resume);
    function apply(){
      if (reduce.matches){ stop(); return; } // reduzierte Bewegung: statisch
      measure(); start();
    }
    if (reduce.addEventListener) reduce.addEventListener('change', apply);
    window.addEventListener('resize', measure);
    apply();
  });
})();
</script>

Fallender Text

Pol 1 · Craft Anlass: verspielter Auftritt

Buchstaben fallen beim Trigger gestaffelt von oben herein und federn per Bounce-Easing in ihre Position — verspielter Hero- oder Sektions-Auftritt. Vorbild: reactbits FallingText.

Ideen, die auffallen.

<style>
/* Fallender Text — gestaffelter Buchstaben-Einfall mit Bounce (Scope: te14-) */
.te14-stage{display:flex;flex-direction:column;align-items:center;gap:1.3rem;
  min-height:180px;justify-content:center;text-align:center;width:100%}
.te14-headline{margin:0;font-size:clamp(1.7rem,6vw,2.9rem);font-weight:800;
  line-height:1.14;letter-spacing:-.01em;color:#f4f4f5;
  display:flex;flex-wrap:wrap;justify-content:center;gap:0 .28em}
.te14-word{display:inline-flex;white-space:nowrap}
.te14-char{display:inline-block;transform-origin:50% 100%;will-change:transform,opacity}
.te14-char--accent{color:var(--akzent,#7c87ff)}
/* Ausgangszustand: oben, gedreht, unsichtbar */
.te14-headline[data-state="in"] .te14-char{
  opacity:0;transform:translateY(-42px) rotate(-14deg)}
/* Ziel: eingefallen, mit federndem Bounce-Easing, pro Buchstabe gestaffelt */
.te14-headline[data-state="run"] .te14-char{
  opacity:1;transform:translateY(0) rotate(0);
  transition:transform .58s cubic-bezier(.34,1.56,.64,1),opacity .34s ease-out;
  transition-delay:calc(var(--te14-i,0) * 42ms)}
.te14-replay{display:inline-flex;align-items:center;gap:.5rem;
  padding:.55rem 1.05rem;border:1px solid #26262c;border-radius:99px;
  background:#16161a;color:#e9e9ee;font-size:.85rem;font-family:inherit;line-height:1;
  cursor:pointer;transition:border-color .2s ease,background .2s ease,transform .1s ease}
.te14-replay:hover{border-color:var(--akzent,#7c87ff);background:#1c1c22}
.te14-replay:active{transform:translateY(1px)}
.te14-replay:focus-visible{outline:2px solid var(--akzent,#7c87ff);outline-offset:2px}
.te14-replay svg{width:15px;height:15px;flex:0 0 auto}
/* Motion aus: sofort Endzustand, keine Bewegung */
@media (prefers-reduced-motion:reduce){
  .te14-headline[data-state="in"] .te14-char,
  .te14-headline[data-state="run"] .te14-char{
    opacity:1;transform:none;transition:none;transition-delay:0s}
  .te14-replay{transition:none}
}
</style>
<article class="fx" id="te14-1">
  <div class="fx-meta">
    <h3>Fallender Text</h3>
    <span class="fx-tag pol1">Pol 1 · Craft</span>
    <span class="fx-tag">Anlass: verspielter Auftritt</span>
  </div>
  <p class="fx-note">Buchstaben fallen beim Trigger gestaffelt von oben herein und federn per Bounce-Easing in ihre Position — verspielter Hero- oder Sektions-Auftritt. Vorbild: reactbits FallingText.</p>
  <div class="stage">
    <div class="te14-stage">
      <h2 class="te14-headline" data-te14-text data-te14-accent="2">Ideen, die auffallen.</h2>
      <button type="button" class="te14-replay" data-te14-replay>
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 3v5h5"/></svg>
        Erneut abspielen
      </button>
    </div>
  </div>
</article>
<script>
(function(){
  var REDUCE = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  function build(head){
    if(head.dataset.te14Ready) return;
    head.dataset.te14Ready = '1';
    var text = head.textContent.trim();
    var accentIdx = parseInt(head.getAttribute('data-te14-accent'), 10) || 0; // 1-basiert, 0 = keins
    var words = text.split(/\s+/);
    head.textContent = '';
    var i = 0;
    words.forEach(function(word, wi){
      var w = document.createElement('span');
      w.className = 'te14-word';
      var isAccent = (wi + 1 === accentIdx);
      for(var c = 0; c < word.length; c++){
        var ch = document.createElement('span');
        ch.className = isAccent ? 'te14-char te14-char--accent' : 'te14-char';
        ch.style.setProperty('--te14-i', i);
        ch.textContent = word.charAt(c);
        w.appendChild(ch);
        i++;
      }
      head.appendChild(w);
    });
    head.setAttribute('data-state', 'in');
  }

  function play(head){
    if(REDUCE){ head.setAttribute('data-state', 'run'); return; }
    head.setAttribute('data-state', 'in');
    void head.offsetWidth; // Reflow: Transition sauber neu starten
    requestAnimationFrame(function(){
      requestAnimationFrame(function(){ head.setAttribute('data-state', 'run'); });
    });
  }

  function init(stage){
    if(stage.dataset.te14Init) return;
    stage.dataset.te14Init = '1';
    var head = stage.querySelector('[data-te14-text]');
    if(!head) return;
    build(head);
    var btn = stage.querySelector('[data-te14-replay]');
    if(btn) btn.addEventListener('click', function(){ play(head); });

    if(REDUCE){ head.setAttribute('data-state', 'run'); return; }
    if('IntersectionObserver' in window){
      var io = new IntersectionObserver(function(entries){
        entries.forEach(function(e){
          if(e.isIntersecting){ play(head); io.unobserve(e.target); }
        });
      }, { threshold: .5 });
      io.observe(head);
    } else {
      play(head);
    }
  }

  document.querySelectorAll('.te14-stage').forEach(init);
})();
</script>

Text-Scroll-Reveal (Wort für Wort)

Pol 2 · Klarheit Anlass: Fließtext beim Scrollen

Die Wörter eines Absatzes decken sich beim Scrollen einzeln von gedämpft auf voll auf; der Fortschritt hängt direkt an der Scroll-Position der Box, ein Replay-Button spielt es ohne Scrollen ab. Vorbild: reactbits ScrollReveal.

Gute Gestaltung führt den Blick, statt ihn zu fordern. Jedes Wort tritt genau dann hervor, wenn es gebraucht wird — ruhig, klar und ohne Umweg.

<div class="te15-box" data-te15-box tabindex="0" aria-label="Scrollbarer Text-Reveal">
  <div class="te15-rail" aria-hidden="true"><div class="te15-rail-fill" data-te15-rail></div></div>
  <div class="te15-pad" aria-hidden="true">scrollen</div>
  <p class="te15-text" data-te15-text>Gute Gestaltung führt den Blick, statt ihn zu fordern …</p>
  <div class="te15-pad" aria-hidden="true">Ende</div>
</div>
<button class="te15-replay" type="button" data-te15-replay>Erneut abspielen</button>

<style>
.te15-box{position:relative;max-width:520px;height:190px;overflow-y:auto;overflow-x:hidden;
  border:1px solid #26262c;border-radius:14px;background:linear-gradient(#1c1c22,#16161a);
  padding:0 28px;scroll-behavior:smooth}
.te15-box:focus-visible{outline:2px solid var(--akzent,#7c87ff);outline-offset:3px}
.te15-pad{height:120px;display:flex;align-items:center;justify-content:center;color:#a1a1aa;
  font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;user-select:none}
.te15-text{font-size:1.28rem;line-height:1.95;font-weight:600;color:#e9e9ee;margin:0}
.te15-word{display:inline-block;color:#e9e9ee;opacity:.2;filter:blur(.6px);transform:translateY(1px);
  transition:opacity .42s cubic-bezier(.22,.61,.36,1),filter .42s cubic-bezier(.22,.61,.36,1),
             color .42s linear,transform .42s cubic-bezier(.22,.61,.36,1)}
.te15-word.te15-on{opacity:1;filter:blur(0);color:#f4f4f5;transform:none}
.te15-rail{position:absolute;left:0;top:0;width:3px;height:100%;background:#26262c;overflow:hidden;pointer-events:none}
.te15-rail-fill{position:absolute;left:0;top:0;width:100%;height:0%;background:var(--akzent,#7c87ff);transition:height .18s linear}
@media (prefers-reduced-motion:reduce){
  .te15-box{scroll-behavior:auto}
  .te15-word{transition:none;opacity:1;filter:none;transform:none;color:#f4f4f5}
  .te15-rail-fill{transition:none}
}
</style>

<script>
/* Wörter in Spans zerlegen; Reveal-Fortschritt an Scroll-Position der Box koppeln.
   Mehrfach-sicher: jede .te15-box wird eigenständig verdrahtet. */
(function(){
  var reduce = matchMedia('(prefers-reduced-motion:reduce)').matches;
  document.querySelectorAll('[data-te15-box]').forEach(function(box){
    if(box.dataset.te15Ready) return; box.dataset.te15Ready = '1';
    var p = box.querySelector('[data-te15-text]');
    var rail = box.querySelector('[data-te15-rail]');
    if(!p) return;

    /* Text einmalig in Wort-Spans umschließen (Whitespace bleibt erhalten) */
    var words = [];
    p.innerHTML = p.textContent.trim().split(/(\s+)/).map(function(tok){
      if(/^\s+$/.test(tok)) return tok;
      return '<span class="te15-word">'+tok+'</span>';
    }).join('');
    words = Array.prototype.slice.call(p.querySelectorAll('.te15-word'));

    if(reduce){ words.forEach(function(w){ w.classList.add('te15-on'); }); if(rail) rail.style.height='100%'; return; }

    /* Scroll-Fortschritt 0..1 → Anteil aufgedeckter Wörter */
    function apply(prog){
      prog = Math.max(0, Math.min(1, prog));
      var n = Math.round(prog * words.length);
      words.forEach(function(w,i){ w.classList.toggle('te15-on', i < n); });
      if(rail) rail.style.height = (prog*100).toFixed(1)+'%';
    }
    var raf = 0;
    function onScroll(){
      if(raf) return;
      raf = requestAnimationFrame(function(){
        raf = 0;
        var max = box.scrollHeight - box.clientHeight;
        apply(max > 0 ? box.scrollTop / max : 1);
      });
    }
    box.addEventListener('scroll', onScroll, {passive:true});
    onScroll();

    /* Replay-Button: sanft nach unten und wieder hoch scrollen = Effekt läuft ab */
    var btn = box.parentNode && box.parentNode.querySelector('[data-te15-replay]');
    if(btn){
      btn.addEventListener('click', function(){
        var max = box.scrollHeight - box.clientHeight;
        btn.setAttribute('data-running','1');
        box.scrollTo({top:0, behavior:'auto'});
        requestAnimationFrame(function(){
          box.scrollTo({top:max, behavior:'smooth'});
        });
        setTimeout(function(){ btn.removeAttribute('data-running'); }, 900);
      });
    }
  });
})();
</script>

Geschwindigkeits-Lauftext

Pol 1 · Craft Anlass: Showcase-Band

Zwei gegenläufige Endlos-Textbänder mit großer gedämpfter Typo und Rand-Fades, die bei Hover pausieren — als Blickfang-Streifen für Showcase-Sektionen. Vorbild: reactbits ScrollVelocity.

<style>
/* ===== Geschwindigkeits-Lauftext · scoped über te16- ===== */
.te16-stage{
  --te16-akzent: var(--akzent,#7c87ff);
  position:relative;
  display:flex;
  flex-direction:column;
  gap:.4rem;
  padding:2.2rem 0;
  background:#16161a;
  border:1px solid #26262c;
  border-radius:14px;
  overflow:hidden;
  /* Rand-Fades links/rechts über Maske */
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 12%,#000 88%,transparent 100%);
          mask-image:linear-gradient(90deg,transparent 0,#000 12%,#000 88%,transparent 100%);
}

/* eine Zeile = Viewport-Streifen, versteckt den Überlauf */
.te16-row{
  display:flex;
  width:max-content;
  will-change:transform;
}

/* das Band selbst: zwei identische Kopien hintereinander → nahtlose Schleife */
.te16-track{
  display:flex;
  flex:none;
  align-items:center;
  gap:2.5rem;
  padding-right:2.5rem;
}

/* obere Reihe wandert nach links, untere nach rechts */
.te16-row--left{ animation:te16-scroll-left 22s linear infinite; }
.te16-row--right{ animation:te16-scroll-right 26s linear infinite; }

/* Pause bei Hover/Fokus innerhalb der Bühne */
.te16-stage:hover .te16-row,
.te16-stage:focus-within .te16-row{ animation-play-state:paused; }

.te16-word{
  font:800 clamp(2rem,7vw,4.2rem)/1 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  letter-spacing:-.02em;
  color:#1c1c22;                 /* gedämpfte Grund-Typo */
  -webkit-text-stroke:1px #26262c;
  text-transform:uppercase;
  white-space:nowrap;
  user-select:none;
  transition:color .3s ease;
}
/* jedes dritte Wort im Akzent, damit das Band Rhythmus bekommt */
.te16-word:nth-child(3n+1){
  color:var(--te16-akzent);
  -webkit-text-stroke:0;
  opacity:.9;
}

/* dezenter Fokus-Ring für Tastatur-Bedienung */
.te16-stage:focus-visible{
  outline:2px solid var(--te16-akzent);
  outline-offset:3px;
}

@keyframes te16-scroll-left{
  from{ transform:translate3d(0,0,0); }
  to  { transform:translate3d(-50%,0,0); }   /* halbe Breite = eine Kopie */
}
@keyframes te16-scroll-right{
  from{ transform:translate3d(-50%,0,0); }
  to  { transform:translate3d(0,0,0); }
}

/* Motion aus: Bänder anhalten, Effekt im Endzustand zeigen */
@media (prefers-reduced-motion: reduce){
  .te16-row--left,
  .te16-row--right{ animation:none; transform:translate3d(-25%,0,0); }
}
</style>

<div class="te16-stage" tabindex="0" aria-label="Endlos laufendes Textband">
  <div class="te16-row te16-row--left" aria-hidden="true">
    <div class="te16-track">
      <span class="te16-word">Qualität</span><span class="te16-word">Präzision</span><span class="te16-word">Handwerk</span><span class="te16-word">Design</span><span class="te16-word">Studio</span>
    </div>
    <div class="te16-track">
      <span class="te16-word">Qualität</span><span class="te16-word">Präzision</span><span class="te16-word">Handwerk</span><span class="te16-word">Design</span><span class="te16-word">Studio</span>
    </div>
  </div>
  <div class="te16-row te16-row--right" aria-hidden="true">
    <div class="te16-track">
      <span class="te16-word">Velocity</span><span class="te16-word">Motion</span><span class="te16-word">Rhythmus</span><span class="te16-word">Flow</span><span class="te16-word">Energie</span>
    </div>
    <div class="te16-track">
      <span class="te16-word">Velocity</span><span class="te16-word">Motion</span><span class="te16-word">Rhythmus</span><span class="te16-word">Flow</span><span class="te16-word">Energie</span>
    </div>
  </div>
</div>

Schwebender Einflug

Pol 1 · Craft Anlass: Headline-Auftritt

Die Buchstaben der Ueberschrift schweben beim Erscheinen einzeln und leicht versetzt von unten ein — fuer einen ruhigen, hochwertigen Auftritt. Vorbild: reactbits ScrollFloat.

Neu im Sortiment

Schön, dass Sie da sind.

<style>
/* ===== te17 · Schwebender Einflug ===== */
.te17-stage{
  --te17-accent: var(--akzent,#7c87ff);
  position:relative;
  display:flex; flex-direction:column; align-items:flex-start; justify-content:center;
  gap:18px;
  min-height:200px;
  padding:36px 32px;
  border:1px solid #26262c;
  border-radius:14px;
  background:
    radial-gradient(120% 140% at 12% 0%, rgba(124,135,255,.10), transparent 60%),
    linear-gradient(180deg,#1c1c22,#16161a);
  overflow:hidden;
}
.te17-eyebrow{
  margin:0;
  font-size:12px; letter-spacing:.18em; text-transform:uppercase;
  color:#a1a1aa;
}
.te17-headline{
  margin:0;
  font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  font-size:clamp(28px,6vw,44px);
  line-height:1.08;
  font-weight:650;
  letter-spacing:-.01em;
  color:#f4f4f5;
  max-width:14ch;
  text-wrap:balance;
}
/* Wort-Huelle: haelt Buchstaben eines Wortes zusammen (kein Umbruch mitten im Wort) */
.te17-word{ display:inline-block; white-space:nowrap; }
.te17-char{
  display:inline-block;
  white-space:pre;
  will-change:transform,opacity;
  opacity:0;
  transform:translateY(24px);
}
/* Einflug: nur transform/opacity, weiches Ausklingen, Delay pro Buchstabe */
.te17-stage.is-playing .te17-char{
  animation:te17-rise 520ms cubic-bezier(.22,.68,.28,1) both;
  animation-delay:calc(var(--te17-i,0) * 36ms);
}
@keyframes te17-rise{
  from{ opacity:0; transform:translateY(24px); }
  to  { opacity:1; transform:translateY(0); }
}
.te17-replay{
  display:inline-flex; align-items:center; gap:8px;
  margin-top:2px;
  padding:9px 15px;
  font:inherit; font-size:14px; font-weight:550; line-height:1;
  color:#e9e9ee;
  background:#1c1c22;
  border:1px solid #26262c;
  border-radius:9px;
  cursor:pointer;
  transition:border-color 180ms ease, transform 180ms ease, background 180ms ease;
}
.te17-replay:hover{ border-color:var(--te17-accent); background:#20202a; }
.te17-replay:active{ transform:translateY(1px); }
.te17-replay:focus-visible{ outline:2px solid var(--te17-accent); outline-offset:2px; }
.te17-replay svg{ width:15px; height:15px; flex:none; }
/* Motion aus: Endzustand sofort, keine Bewegung */
@media (prefers-reduced-motion: reduce){
  .te17-char{ opacity:1; transform:none; }
  .te17-stage.is-playing .te17-char{ animation:none; }
  .te17-replay{ transition:none; }
}
</style>

<div class="te17-stage">
  <p class="te17-eyebrow">Neu im Sortiment</p>
  <h3 class="te17-headline" data-te17-text="Schön, dass Sie da sind.">Schön, dass Sie da sind.</h3>
  <button type="button" class="te17-replay">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v4h4"/>
    </svg>
    Erneut abspielen
  </button>
</div>

<script>
(function(){
  var stages = document.querySelectorAll('.te17-stage');
  Array.prototype.forEach.call(stages, function(stage){
    if(stage.dataset.te17Ready) return;   // Mehrfach-Init verhindern
    stage.dataset.te17Ready = '1';

    var headline = stage.querySelector('.te17-headline');
    var replay   = stage.querySelector('.te17-replay');
    if(!headline) return;
    var text = headline.getAttribute('data-te17-text') || headline.textContent;

    // Text in einzeln animierbare Buchstaben zerlegen.
    // Woerter bleiben als Huelle zusammen; Leerzeichen animieren mit.
    function build(){
      headline.textContent = '';
      var i = 0;
      var words = text.split(' ');
      words.forEach(function(word, wi){
        var wrap = document.createElement('span');
        wrap.className = 'te17-word';
        for(var c = 0; c < word.length; c++){
          var s = document.createElement('span');
          s.className = 'te17-char';
          s.style.setProperty('--te17-i', i++);
          s.textContent = word.charAt(c);
          wrap.appendChild(s);
        }
        headline.appendChild(wrap);
        if(wi < words.length - 1){
          var sp = document.createElement('span');
          sp.className = 'te17-char';
          sp.style.setProperty('--te17-i', i++);
          sp.textContent = ' ';
          headline.appendChild(sp);
        }
      });
    }

    function play(){
      stage.classList.remove('is-playing');
      void stage.offsetWidth;            // Reflow: Animation sauber neu starten
      stage.classList.add('is-playing');
    }

    build();
    play();
    if(replay){ replay.addEventListener('click', play); }
  });
})();
</script>

Shuffle-Text (Einsortieren)

Pol 1 · Craft Anlass: Headline-Auftritt

Die Buchstaben erscheinen verstreut und in zufälliger Reihenfolge und sortieren sich dann animiert an ihren richtigen Platz – für einen aufmerksamkeitsstarken Headline-Auftritt. Vorbild: reactbits Shuffle.

Neu im Sortiment

Buchstaben finden ihren Platz

<style>
.te18-stage{
  --akzent:#7c87ff;
  position:relative;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:22px;
  min-height:230px;
  padding:40px 28px;
  background:#16161a;
  border:1px solid #26262c;
  border-radius:16px;
  overflow:hidden;
}
.te18-stage::before{
  content:"";
  position:absolute;
  inset:0;
  background:radial-gradient(120% 90% at 50% 0%, color-mix(in srgb, var(--akzent,#7c87ff) 10%, transparent), transparent 60%);
  pointer-events:none;
}
.te18-eyebrow{
  position:relative;
  margin:0;
  font-size:.72rem;
  letter-spacing:.22em;
  text-transform:uppercase;
  color:var(--akzent,#7c87ff);
  font-weight:600;
}
.te18-headline{
  position:relative;
  margin:0;
  font-size:clamp(1.7rem, 6vw, 2.9rem);
  font-weight:800;
  line-height:1.08;
  letter-spacing:-.02em;
  color:#f4f4f5;
  text-align:center;
  /* Buchstaben liegen nebeneinander, jeder einzeln animierbar */
  display:flex;
  flex-wrap:wrap;
  justify-content:center;
}
.te18-char{
  display:inline-block;
  /* Startzustand kommt aus per JS gesetzten CSS-Variablen; Endzustand = alles 0 / voll sichtbar */
  transform:translate(var(--tx,0), var(--ty,0)) rotate(var(--rot,0deg)) scale(var(--sc,1));
  opacity:var(--op,1);
  transition:
    transform 520ms cubic-bezier(.22,1,.36,1) var(--delay,0ms),
    opacity 360ms ease var(--delay,0ms);
  will-change:transform, opacity;
}
.te18-char.te18-space{ width:.34em; }
.te18-controls{
  position:relative;
  display:flex;
  gap:10px;
  align-items:center;
  flex-wrap:wrap;
  justify-content:center;
}
.te18-btn{
  display:inline-flex;
  align-items:center;
  gap:8px;
  padding:9px 16px;
  font:inherit;
  font-size:.86rem;
  font-weight:600;
  color:#e9e9ee;
  background:#1c1c22;
  border:1px solid #26262c;
  border-radius:10px;
  cursor:pointer;
  transition:border-color 180ms ease, background 180ms ease, transform 120ms ease;
}
.te18-btn:hover{
  border-color:color-mix(in srgb, var(--akzent,#7c87ff) 55%, #26262c);
  background:#22222a;
}
.te18-btn:active{ transform:translateY(1px); }
.te18-btn:focus-visible{
  outline:2px solid var(--akzent,#7c87ff);
  outline-offset:2px;
}
.te18-btn svg{ width:15px; height:15px; }
.te18-hint{
  position:relative;
  margin:0;
  font-size:.78rem;
  color:#a1a1aa;
}

@media (prefers-reduced-motion: reduce){
  .te18-char{
    transition:none !important;
    /* Endzustand erzwingen: kein Versatz, voll sichtbar */
    transform:none !important;
    opacity:1 !important;
  }
  .te18-btn{ transition:none; }
}
</style>

<div class="te18-stage">
  <p class="te18-eyebrow">Neu im Sortiment</p>
  <h2 class="te18-headline" data-te18-shuffle data-text="Ordnung entsteht"></h2>
  <div class="te18-controls">
    <button type="button" class="te18-btn" data-te18-replay>
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v4h4"/>
      </svg>
      Neu einsortieren
    </button>
    <p class="te18-hint">Buchstaben finden ihren Platz</p>
  </div>
</div>

<script>
(function(){
  "use strict";
  var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

  function rnd(min, max){ return Math.random() * (max - min) + min; }

  function buildChars(headline){
    var text = headline.getAttribute("data-text") || headline.textContent || "";
    headline.textContent = "";
    var spans = [];
    for (var i = 0; i < text.length; i++){
      var ch = text[i];
      var span = document.createElement("span");
      span.className = "te18-char";
      if (ch === " "){
        span.classList.add("te18-space");
        span.setAttribute("aria-hidden", "true");
      } else {
        span.textContent = ch;
      }
      headline.appendChild(span);
      spans.push(span);
    }
    // Kompletten Text als Label fuer Screenreader anbieten
    headline.setAttribute("aria-label", text);
    return spans;
  }

  function scatter(spans){
    // Zufaellige Startpositionen/-drehungen setzen; Ziel ist immer 0
    for (var i = 0; i < spans.length; i++){
      var s = spans[i];
      s.style.setProperty("--tx", rnd(-90, 90).toFixed(1) + "px");
      s.style.setProperty("--ty", rnd(-60, 60).toFixed(1) + "px");
      s.style.setProperty("--rot", rnd(-45, 45).toFixed(1) + "deg");
      s.style.setProperty("--sc", rnd(.4, 1.3).toFixed(2));
      s.style.setProperty("--op", "0");
      s.style.setProperty("--delay", "0ms");
    }
  }

  function settle(spans){
    // In ZUFAELLIGER Reihenfolge einsortieren -> gestaffelte Delays je Buchstabe
    var order = spans.map(function(_, i){ return i; });
    for (var i = order.length - 1; i > 0; i--){
      var j = Math.floor(Math.random() * (i + 1));
      var t = order[i]; order[i] = order[j]; order[j] = t;
    }
    order.forEach(function(idx, step){
      var s = spans[idx];
      s.style.setProperty("--delay", (step * 34) + "ms");
      s.style.setProperty("--tx", "0px");
      s.style.setProperty("--ty", "0px");
      s.style.setProperty("--rot", "0deg");
      s.style.setProperty("--sc", "1");
      s.style.setProperty("--op", "1");
    });
  }

  function play(spans){
    if (reduce){
      // Bei reduzierter Bewegung sofort Endzustand zeigen, keine Animation
      spans.forEach(function(s){
        s.style.setProperty("--tx","0px");
        s.style.setProperty("--ty","0px");
        s.style.setProperty("--rot","0deg");
        s.style.setProperty("--sc","1");
        s.style.setProperty("--op","1");
      });
      return;
    }
    scatter(spans);
    // Reflow erzwingen, damit der Startzustand wirklich rendert, dann einsortieren
    void spans[0].offsetWidth;
    requestAnimationFrame(function(){
      requestAnimationFrame(function(){ settle(spans); });
    });
  }

  function init(stage){
    if (stage.dataset.te18Ready) return;   // mehrfach-sicher
    stage.dataset.te18Ready = "1";
    var headline = stage.querySelector("[data-te18-shuffle]");
    var replay = stage.querySelector("[data-te18-replay]");
    if (!headline) return;
    var spans = buildChars(headline);

    play(spans);

    if (replay){
      replay.addEventListener("click", function(){ play(spans); });
    }

    // Dezente Endlos-Schleife, damit der Effekt in der kleinen Buehne sichtbar bleibt
    if (!reduce){
      var loopTotal = spans.length * 34 + 520 + 5200;
      var timer = setInterval(function(){
        if (!document.body.contains(stage)){ clearInterval(timer); return; }
        play(spans);
      }, loopTotal);
    }
  }

  // Alle Buehnen auf der Seite verdrahten (Praefix-scoped)
  var stages = document.querySelectorAll(".te18-stage");
  for (var i = 0; i < stages.length; i++){ init(stages[i]); }
})();
</script>