MediaWiki:Common.js: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Markierung: Zurückgesetzt
Keine Bearbeitungszusammenfassung
Markierung: Manuelle Zurücksetzung
Zeile 1.050: Zeile 1.050:
   navigator.serviceWorker.register('/app/labelscan/sw.js').catch(function(){});
   navigator.serviceWorker.register('/app/labelscan/sw.js').catch(function(){});
}
}
/* =========================================================
  ADOS XMAS: Timer-Bar & Snow (nur im Dezember)
  ========================================================= */
(function () {
  'use strict';
  if (typeof window === 'undefined' || typeof document === 'undefined') return;
  var now = new Date();
  // Nur im Dezember aktiv (Monat 11 = Dezember)
  if (now.getMonth() !== 11) {
    return;
  }
  // Wenn der Nutzer reduzierte Animationen bevorzugt → Schneefall aus
  var prefersReducedMotion = false;
  try {
    prefersReducedMotion = window.matchMedia &&
      window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  } catch (e) {}
  /* -----------------------------
    1) Nur bestehenden Timer „weihnachtlich anziehen“
    ----------------------------- */
  function decorateXmasTimer() {
    var bar = document.getElementById('ados-timer-bar');
    if (!bar) return; // Dein eigener Timer erzeugt dieses Element
    // Weihnachts-Design via CSS-Klasse aktivieren
    bar.classList.add('ados-xmas');
    // Optional: Nachricht dezent mit 🎄 ergänzen (ohne alles zu überschreiben)
    var msgEl = document.getElementById('ados-timer-message');
    if (msgEl && !msgEl.dataset.xmasDecorated) {
      msgEl.textContent = '🎄 ' + msgEl.textContent;
      msgEl.dataset.xmasDecorated = '1';
    }
  }
  /* -----------------------------
    2) Schneefall-Effekt
    ----------------------------- */
  function initSnow() {
    if (prefersReducedMotion) return;
    // Optional: auf sehr kleinen Geräten deaktivieren
    if (window.innerWidth < 600) return;
    // Container anlegen
    var container = document.createElement('div');
    container.id = 'ados-snow';
    document.body.appendChild(container);
    var flakeChars = ['❄', '✻', '✼', '✥', '✶'];
    var flakeCount = 60; // dezent halten
    for (var i = 0; i < flakeCount; i++) {
      var flake = document.createElement('span');
      flake.className = 'ados-snowflake';
      flake.textContent = flakeChars[Math.floor(Math.random() * flakeChars.length)];
      // Zufällige Start-Parameter
      var startX = Math.random() * 100;          // vw
      var drift  = (Math.random() * 40) - 20;    // -20 bis +20 vw
      var dur    = 12 + Math.random() * 10;      // 12–22 Sekunden
      var delay  = Math.random() * 20;          // bis zu 20 Sekunden
      flake.style.left = startX + 'vw';
      flake.style.setProperty('--x-start', '0vw');
      flake.style.setProperty('--x-end', drift.toFixed(1) + 'vw');
      flake.style.animationDuration = dur.toFixed(1) + 's';
      flake.style.animationDelay = delay.toFixed(1) + 's';
      container.appendChild(flake);
    }
  }
  /* -----------------------------
    3) Init nach DOM-Ready
    ----------------------------- */
  function onReady(fn) {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', fn, { once: true });
    } else {
      fn();
    }
  }
  onReady(function () {
    // nur dekorieren, NICHT neu bauen
    decorateXmasTimer();
    initSnow();
  });
})();
/* === ADOS – Winter-Schneefall (Desktop dezent, mobil deutlich sichtbar) === */
(function() {
  // Rücksicht auf Nutzer mit "Bewegung reduzieren"
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
  function initSnow() {
    const isMobile = window.innerWidth < 700;
    const container = document.createElement('div');
    container.id = 'ados-snow';
    container.style.position = 'fixed';
    container.style.top = '0';
    container.style.left = '0';
    container.style.width = '100%';
    container.style.height = '100%';
    container.style.pointerEvents = 'none';
    container.style.zIndex = '9999';
    container.style.overflow = 'hidden';
    document.body.appendChild(container);
    const flakeChars = ['•', '·', '∙'];
    // Desktop dezent, mobil kräftig
    const flakeCount = isMobile ? 60 : 30;
    for (let i = 0; i < flakeCount; i++) {
      const flake = document.createElement('div');
      flake.textContent = flakeChars[Math.floor(Math.random() * flakeChars.length)];
      flake.style.position = 'absolute';
      // Größe: mobil deutlich größer
      const size = isMobile
        ? (12 + Math.random() * 10)      // Mobil: 12–22 px
        : (5 + Math.random() * 4);    // Desktop: 5–9 px
      flake.style.fontSize = size + 'px';
      // Sichtbarkeit (Opacity): mobil deutlich höher
      const opacity = isMobile
        ? (0.45 + Math.random() * 0.4)  // Mobil: 0.45–0.85
        : (0.15 + Math.random() * 0.18); // Desktop: 0.15–0.33
      flake.style.opacity = opacity.toFixed(2);
      flake.style.color = '#ffffff';
      // Startposition
      flake.style.left = Math.random() * 100 + 'vw';
      flake.style.top  = -(Math.random() * 20) + 'vh';
      // Geschwindigkeit: mobil etwas schneller, aber nicht hektisch
      const duration = isMobile
        ? (10 + Math.random() * 12)    // Mobil: 10–22 s
        : (18 + Math.random() * 22);    // Desktop: 18–40 s
      const drift = isMobile ? 18 : 10; // seitliche Drift in px
      flake.style.animation = `adosSnowSoft ${duration}s linear infinite`;
      flake.style.setProperty('--ados-snow-drift', drift + 'px');
      flake.style.animationDelay = (-Math.random() * duration) + 's';
      container.appendChild(flake);
    }
  }
  // Keyframes: benutzen CSS-Variable für Drift
  const style = document.createElement('style');
  style.textContent = `
    @keyframes adosSnowSoft {
      0%  { transform: translateY(-12vh) translateX(0); }
      100% { transform: translateY(110vh) translateX(var(--ados-snow-drift, 10px)); }
    }
  `;
  document.head.appendChild(style);
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initSnow);
  } else {
    initSnow();
  }
})();
/* ============================================================
  ADOS – Feuerwerk (Raketen hoch + Explosion + optional "2026")
  ES5 | dauerhaft | kein Abdunkeln/Overlay | Canvas transparent
  ============================================================ */
(function () {
  'use strict';
  // true = nur 31.12/01.01, false = immer
  var onlyOnNewYears = false;
  // Wahrscheinlichkeit, dass statt normaler Explosion "2026" erscheint
  var YEAR_PROB = 0.15; // 0.10 = seltener, 0.25 = häufiger
  // Raketen-Geschwindigkeit (langsamer = kleinere Beträge)
  var ROCKET_VY_MIN = -6.5;
  var ROCKET_VY_MAX = -9.0;
  var ROCKET_VX_MIN = -0.6;
  var ROCKET_VX_MAX =  0.6;
  // Schwerkraft für Rakete (kleiner = ruhiger)
  var ROCKET_GRAVITY = 0.003;
  function isNewYears() {
    var d = new Date();
    var m = d.getMonth() + 1;
    var day = d.getDate();
    return (m === 12 && day === 31) || (m === 1 && day === 1);
  }
  if (onlyOnNewYears && !isNewYears()) return;
  function createCanvas() {
    var old = document.getElementById('ados-fireworks-canvas');
    if (old && old.parentNode) old.parentNode.removeChild(old);
    var c = document.createElement('canvas');
    c.id = 'ados-fireworks-canvas';
    c.style.position = 'fixed';
    c.style.left = '0';
    c.style.top = '0';
    c.style.width = '100%';
    c.style.height = '100%';
    c.style.pointerEvents = 'none';
    c.style.zIndex = '9999';
    c.style.opacity = '1';
    document.body.appendChild(c);
    return c;
  }
  function fitCanvas(c) {
    var dpr = window.devicePixelRatio || 1;
    c.width = Math.floor(window.innerWidth * dpr);
    c.height = Math.floor(window.innerHeight * dpr);
    var ctx = c.getContext('2d');
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    // Glow/Leuchteffekt
    ctx.globalCompositeOperation = 'lighter';
    return ctx;
  }
  function rand(min, max) { return min + Math.random() * (max - min); }
  function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }
  var canvas, ctx;
  var rockets = [];
  var particles = [];
  var last = 0;
  var running = true;
  // ------------------------------------------------------------
  // "2026" Punkt-Matrix (5x7)
  // ------------------------------------------------------------
  function getDigitPoints(digit) {
    var map = {
      '0': [
        "01110",
        "10001",
        "10011",
        "10101",
        "11001",
        "10001",
        "01110"
      ],
      '2': [
        "01110",
        "10001",
        "00001",
        "00010",
        "00100",
        "01000",
        "11111"
      ],
      '6': [
        "00110",
        "01000",
        "10000",
        "11110",
        "10001",
        "10001",
        "01110"
      ]
    };
    return map[digit] || [];
  }
  // ------------------------------------------------------------
  // Rakete erzeugen
  // ------------------------------------------------------------
  function spawnRocket() {
    var w = window.innerWidth;
    var h = window.innerHeight;
    var x = rand(60, w - 60);
    var y = h + rand(20, 120);
    // Explosionshöhe
    var targetY = rand(h * 0.12, h * 0.48);
    // LANGSAMERES Hochschießen (konfig oben)
    var vy = rand(ROCKET_VY_MIN, ROCKET_VY_MAX);
    var vx = rand(ROCKET_VX_MIN, ROCKET_VX_MAX);
    // warmes Leuchten
    var r = Math.floor(rand(220, 255));
    var g = Math.floor(rand(170, 240));
    var b = Math.floor(rand(80, 170));
    rockets.push({
      x: x, y: y,
      vx: vx, vy: vy,
      targetY: targetY,
      age: 0,
      life: rand(2400, 3400),
      r: r, g: g, b: b,
      trail: [],
      showYear: (Math.random() < YEAR_PROB)
    });
  }
  // ------------------------------------------------------------
  // Normale Explosion
  // ------------------------------------------------------------
  function explode(x, y) {
    var count = Math.floor(rand(55, 95));
    var i;
    for (i = 0; i < count; i++) {
      var angle = rand(0, Math.PI * 2);
      var speed = rand(2.8, 6.8);
      var rr = Math.floor(rand(120, 255));
      var gg = Math.floor(rand(80, 240));
      var bb = Math.floor(rand(120, 255));
      // Gold/Amber Bias
      if (Math.random() < 0.35) {
        rr = Math.floor(rand(220, 255));
        gg = Math.floor(rand(150, 230));
        bb = Math.floor(rand(30, 120));
      }
      particles.push({
        x: x, y: y,
        vx: Math.cos(angle) * speed,
        vy: Math.sin(angle) * speed,
        age: 0,
        life: rand(900, 1500),
        size: rand(1.8, 3.4),
        r: rr, g: gg, b: bb,
        type: 'normal'
      });
    }
  }
  // ------------------------------------------------------------
  // "2026" Explosion (Punkte ziehen sich kurz zur Zahl)
  // ------------------------------------------------------------
  function explode2026(cx, cy) {
    var digits = ['2', '0', '2', '6'];
    // Optik-Parameter
    var spacing = 34; // Abstand zwischen Ziffern
    var pixel = 6;    // Punktabstand innerhalb Ziffer
    var rowsH = 7 * pixel;
    // Gesamtbreite grob berechnen: 4 Ziffern * (5*pixel) + 3*spacing
    var totalW = (4 * (5 * pixel)) + (3 * spacing);
    var startX = cx - (totalW / 2);
    var baseY = cy - (rowsH / 2);
    var dx = startX;
    var d, y, x, mat;
    for (d = 0; d < digits.length; d++) {
      mat = getDigitPoints(digits[d]);
      for (y = 0; y < mat.length; y++) {
        for (x = 0; x < mat[y].length; x++) {
          if (mat[y].charAt(x) === '1') {
            // Partikel startet am Explosionspunkt und „zieht“ zur Zielposition
            particles.push({
              x: cx,
              y: cy,
              vx: rand(-1.2, 1.2),
              vy: rand(-1.2, 1.2),
              tx: dx + x * pixel,
              ty: baseY + y * pixel,
              age: 0,
              life: 1800,
              size: 2.6,
              r: 255,
              g: 200,
              b: 80,
              type: 'digit'
            });
          }
        }
      }
      dx += (5 * pixel) + spacing;
    }
  }
  // ------------------------------------------------------------
  // Frame
  // ------------------------------------------------------------
  function tick(ts) {
    if (!running) return;
    if (!last) last = ts;
    var dt = ts - last;
    last = ts;
    var w = window.innerWidth;
    var h = window.innerHeight;
    // Transparent löschen: kein Abdunkeln, kein Weiß-Schleier
    ctx.globalCompositeOperation = 'source-over';
    ctx.clearRect(0, 0, w, h);
    ctx.globalCompositeOperation = 'lighter';
    // ---------------- Rockets ----------------
    var i, r;
    for (i = rockets.length - 1; i >= 0; i--) {
      r = rockets[i];
      r.age += dt;
      // Trail
      r.trail.push({ x: r.x, y: r.y });
      if (r.trail.length > 16) r.trail.shift();
      // Physik (langsamer & ruhiger)
      r.vy += ROCKET_GRAVITY * (dt / 16);
      r.x += r.vx * (dt / 16);
      r.y += r.vy * (dt / 16);
      // Explodieren
      if (r.y <= r.targetY) {
        if (r.showYear) explode2026(r.x, r.y);
        else explode(r.x, r.y);
        rockets.splice(i, 1);
        continue;
      }
      // Safety
      if (r.age >= r.life || r.y < -200 || r.x < -200 || r.x > w + 200) {
        rockets.splice(i, 1);
        continue;
      }
      // Trail zeichnen
      ctx.beginPath();
      ctx.lineWidth = 2.6;
      ctx.strokeStyle = 'rgba(' + r.r + ',' + r.g + ',' + r.b + ',0.30)';
      var t;
      for (t = 0; t < r.trail.length; t++) {
        var pt = r.trail[t];
        if (t === 0) ctx.moveTo(pt.x, pt.y);
        else ctx.lineTo(pt.x, pt.y);
      }
      ctx.stroke();
      // Kopf
      ctx.beginPath();
      ctx.fillStyle = 'rgba(' + r.r + ',' + r.g + ',' + r.b + ',1)';
      ctx.arc(r.x, r.y, 2.7, 0, Math.PI * 2, false);
      ctx.fill();
    }
    // ---------------- Particles ----------------
    var p;
    for (i = particles.length - 1; i >= 0; i--) {
      p = particles[i];
      p.age += dt;
      if (p.age >= p.life) {
        particles.splice(i, 1);
        continue;
      }
      // "2026"-Partikel: zuerst zur Zielposition ziehen, dann zerfallen lassen
      if (p.type === 'digit' && p.age < 900) {
        // sanftes „Anziehen“ zur Zahl
        p.x += (p.tx - p.x) * 0.085;
        p.y += (p.ty - p.y) * 0.085;
      } else {
        // normales Partikel-Verhalten
        p.vy += 0.018 * (dt / 16);
        p.vx *= Math.pow(0.986, dt / 16);
        p.vy *= Math.pow(0.986, dt / 16);
        p.x += p.vx * (dt / 16);
        p.y += p.vy * (dt / 16);
      }
      var alpha = clamp(1 - (p.age / p.life), 0, 1);
      ctx.beginPath();
      ctx.fillStyle = 'rgba(' + p.r + ',' + p.g + ',' + p.b + ',' + alpha + ')';
      ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2, false);
      ctx.fill();
    }
    requestAnimationFrame(tick);
  }
  // ------------------------------------------------------------
  // Spawn-Frequenz (klassisch, aber nicht Dauerfeuer)
  // ------------------------------------------------------------
  function scheduleRockets() {
    function loop() {
      if (!running) return;
      // meist 1 Rakete, manchmal 2
      spawnRocket();
      if (Math.random() < 0.28) spawnRocket();
      setTimeout(loop, Math.floor(rand(1200, 2200)));
    }
    loop();
  }
  function init() {
    canvas = createCanvas();
    ctx = fitCanvas(canvas);
    window.addEventListener('resize', function () {
      if (!canvas) return;
      ctx = fitCanvas(canvas);
    });
    scheduleRockets();
    requestAnimationFrame(tick);
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init, false);
  } else {
    init();
  }
})();
})();