MediaWiki:Common.js: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 395: Zeile 395:


});
});
/* Fireworks Popup (v3) – animierter "ADOS"-Schriftzug mit Feuerwerk */
mw.loader.using(['mediawiki.util','jquery']).then(function(){
  (function($, mw){
    'use strict';
    var CONFIG = {
      enabled: true,
      id: 'fireworks_popup_v3',
      title: 'Neu: Abfüllungen bewerten 🎉',
      messageHTML:
        '<p>Ab sofort kannst du im Wiki <strong>jede Abfüllung bewerten</strong> – ' +
        'teile deine Meinung und hilf anderen bei der Auswahl!</p>',
      cta: { text: 'Jetzt bewerten', url: 'https://ados-wiki.de/index.php?title=Spezial:Zuf%C3%A4llige_Seite' },
      showOnNamespaces: 'all',
      dailyLimit: 1,
      escToClose: true,
      clickBackdropToClose: true
    };
    if (!CONFIG.enabled) return;
    var ns = mw.config.get('wgNamespaceNumber');
    if (CONFIG.showOnNamespaces !== 'all' &&
        $.isArray(CONFIG.showOnNamespaces) &&
        $.inArray(ns, CONFIG.showOnNamespaces) === -1) return;
    var isAnon = (mw.config.get('wgUserName') === null);
    function g(k){ try{return localStorage.getItem(k);}catch(e){return null;} }
    function s(k,v){ try{localStorage.setItem(k,v);}catch(e){} }
    var key = 'popup_' + CONFIG.id + (isAnon?':anon':':user');
    var today = (function(d){return d.getFullYear()+'-'+('0'+(d.getMonth()+1)).slice(-2)+'-'+('0'+d.getDate()).slice(-2);})(new Date());
    if (g(key) === today) return;
    function markSeen(){ s(key, today); }
    $(function(){
      var $overlay = $('<div>', {'class':'mw-popup-overlay'});
      var $modal = $('<div>', {'class':'mw-popup-modal','role':'dialog','aria-modal':'true','aria-labelledby':'mw-fw-title'});
      var $fwWrap = $('<div>', {'class':'mw-fw-canvas-wrap'});
      var $canvas = $('<canvas>', {'class':'mw-fw-canvas', 'aria-hidden':'true'});
      $fwWrap.append($canvas);
      var $title = $('<h2>', { id: 'mw-fw-title' }).text(CONFIG.title);
      var $content = $('<div>', {'class':'mw-popup-content'}).html(CONFIG.messageHTML);
      var $buttons = $('<div>', {'class':'mw-popup-button-row'});
      var $ok = $('<button>', {'class':'mw-popup-close', type:'button'}).text('OK');
      $buttons.append($ok);
      if (CONFIG.cta && CONFIG.cta.url) {
        $buttons.append($('<a>', {
          'class':'mw-popup-wiki-button',
          'href': CONFIG.cta.url,
          'target': '_blank',
          'rel': 'noopener'
        }).text(CONFIG.cta.text || 'Mehr'));
      }
      $modal.append($fwWrap, $title, $content, $buttons);
      $('body').append($overlay, $modal);
      function close(){
        stopFireworks();
        markSeen();
        $overlay.remove(); $modal.remove();
        $(document).off('keydown.mwfw');
      }
      $ok.on('click', close);
      if (CONFIG.clickBackdropToClose) $overlay.on('click', close);
      if (CONFIG.escToClose) {
        $(document).on('keydown.mwfw', function(e){
          var k = e.key || e.keyCode;
          if (k==='Escape' || k==='Esc' || k===27){ e.preventDefault(); close(); }
        });
      }
      // ==== Fireworks + animiertes ADOS ====
      var canvas = $canvas[0], ctx = canvas.getContext('2d');
      var dpr = Math.max(1, window.devicePixelRatio || 1);
      var w=0,h=0, raf=null, particles=[], startTime=0;
      var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      function resize(){
        var rect = $fwWrap[0].getBoundingClientRect();
        w = Math.floor(rect.width * dpr);
        h = Math.floor(rect.height * dpr);
        canvas.width = w; canvas.height = h;
        canvas.style.width = rect.width+'px';
        canvas.style.height = rect.height+'px';
      }
      function rand(min,max){ return Math.random()*(max-min)+min; }
      function hsla(h,s,l,a){ return 'hsla('+h+','+s+'%,'+l+'%,'+a+')'; }
      function spawnBurst(x,y){
        var hue = Math.floor(rand(0,360));
        for (var i=0;i<80;i++){
          var angle = rand(0, Math.PI*2);
          var speed = rand(1.2, 3.2);
          particles.push({
            x:x, y:y,
            vx: Math.cos(angle)*speed,
            vy: Math.sin(angle)*speed,
            life: rand(45, 80),
            age: 0,
            hue: hue + rand(-15,15),
          });
        }
      }
      // animierter Schriftzug
      var adosPoints = [];
      function createTextPoints(){
        var tempCanvas = document.createElement('canvas');
        var tctx = tempCanvas.getContext('2d');
        tempCanvas.width = w; tempCanvas.height = h;
        tctx.fillStyle = '#fff';
        tctx.font = 'bold 120px "Segoe UI", Arial, sans-serif';
        tctx.textAlign = 'center';
        tctx.textBaseline = 'middle';
        tctx.fillText('ADOS', w/2, h/2);
        var img = tctx.getImageData(0, 0, w, h).data;
        for (var y=0; y<h; y+=6){
          for (var x=0; x<w; x+=6){
            var i = (y*w + x)*4;
            if (img[i+3] > 128){
              adosPoints.push({x:x, y:y, life:rand(50,100), age:0});
            }
          }
        }
      }
      function drawADOSParticles(){
        for (var i=0; i<adosPoints.length; i++){
          var p = adosPoints[i];
          p.age++;
          var alpha = Math.sin((p.age/p.life)*Math.PI);
          var hue = (p.age*3 + p.x/5) % 360;
          ctx.fillStyle = hsla(hue,100,60,alpha);
          ctx.beginPath();
          ctx.arc(p.x, p.y, 1.6, 0, Math.PI*2);
          ctx.fill();
        }
      }
      function tick(t){
        ctx.fillStyle = 'rgba(0,0,0,0.08)';
        ctx.globalCompositeOperation = 'source-over';
        ctx.fillRect(0,0,w,h);
        ctx.globalCompositeOperation = 'lighter';
        var next = [];
        for (var i=0;i<particles.length;i++){
          var p = particles[i];
          p.age++;
          p.vy += 0.02;
          p.vx *= 0.99; p.vy *= 0.99;
          p.x += p.vx*dpr; p.y += p.vy*dpr;
          var alpha = Math.max(0, 1 - p.age/p.life);
          if (alpha>0){
            ctx.beginPath();
            ctx.fillStyle = hsla(p.hue,100,60,alpha);
            ctx.arc(p.x,p.y,Math.max(0.5,2*alpha),0,Math.PI*2);
            ctx.fill();
            next.push(p);
          }
        }
        particles = next;
        var elapsed = t - startTime;
        if (elapsed < 4000) {
          drawADOSParticles();
        }
        // Feuerwerk nach 2s starten
        if (elapsed > 2000 && Math.random() < 0.08) {
          var bx = rand(w*0.15, w*0.85);
          var by = rand(h*0.2,  h*0.6);
          spawnBurst(bx, by);
        }
        raf = requestAnimationFrame(tick);
      }
      function startFireworks(){
        if (reduceMotion) return;
        resize();
        createTextPoints();
        startTime = performance.now();
        if (!raf) raf = requestAnimationFrame(tick);
        window.addEventListener('resize', resize);
      }
      function stopFireworks(){
        if (raf){ cancelAnimationFrame(raf); raf=null; }
        window.removeEventListener('resize', resize);
      }
      startFireworks();
      markSeen();
    });
  })(jQuery, mw);
});




Zeile 941: Zeile 734:
     m.content = 'light';
     m.content = 'light';
   }
   }
});
// -------------------------------------
/* Whisky News – World of Whisky (Mannheim) – Popup (v1)
  - 1x/Tag je Nutzer
  - Canvas-Animation: Whisky-Glas (Wellen + Bläschen)
  - Zwei Bildkarten mit Links zu den Abfüllungen
*/
mw.loader.using(['mediawiki.util','jquery']).then(function(){
  (function($, mw){
    'use strict';
    var CONFIG = {
      enabled: true,
      id: 'wow_mannheim_whisky_news_v1',      // bei Änderungen hochzählen
      title: 'Whisky News: Messeabfüllungen – World of Whisky (Mannheim)',
      introHTML: '<p>Frisch zur Messe in Mannheim: Zwei limitierte Abfüllungen. Schau sie dir an und bewerte sie im Wiki!</p>',
      // ↓↓↓ BILDER HIER EINTRAGEN (finale URLs aus deinem Wiki!)
      images: [
        {
          src: 'South Islay Single Malt 13 year-old (Sherry Octave Cask Finish).single.jpg', // ERSETZEN
          alt: 'South Islay 13y – Sherry Octave Cask Finish',
          link: 'https://ados-wiki.de/wiki/South_Islay_Single_Malt_13_year-old_(Sherry_Octave_Cask_Finish)',
          cta: 'Zur South Islay Abfüllung'
        },
        {
          src: 'Datei:Tullibardine 13 year-old (Shiraz Wine Octave Cask Finish).single.jpg', // ERSETZEN
          alt: 'Tullibardine 13y – Shiraz Wine Octave Cask Finish',
          link: 'https://ados-wiki.de/wiki/Tullibardine_13_year-old',
          cta: 'Zur Tullibardine Abfüllung'
        }
      ],
      // Anzeige
      showOnNamespaces: 'all',
      dailyLimit: 1,
      escToClose: true,
      clickBackdropToClose: true
    };
    if (!CONFIG.enabled) return;
    var ns = mw.config.get('wgNamespaceNumber');
    if (CONFIG.showOnNamespaces !== 'all' &&
        $.isArray(CONFIG.showOnNamespaces) &&
        $.inArray(ns, CONFIG.showOnNamespaces) === -1) return;
    // 1x/Tag
    var isAnon = (mw.config.get('wgUserName') === null);
    function LSget(k){ try { return localStorage.getItem(k); } catch(e){ return null; } }
    function LSset(k,v){ try { localStorage.setItem(k,v); } catch(e){} }
    var key = 'popup_' + CONFIG.id + (isAnon?':anon':':user');
    var today = (function(d){ return d.getFullYear()+'-'+('0'+(d.getMonth()+1)).slice(-2)+'-'+('0'+d.getDate()).slice(-2); })(new Date());
    if (LSget(key) === today) return;
    function markSeen(){ LSset(key, today); }
    $(function(){
      // Overlay + Modal
      var $overlay = $('<div>', {'class':'mw-popup-overlay'});
      var $modal = $('<div>', {
        'class':'mw-popup-modal',
        'role':'dialog',
        'aria-modal':'true',
        'aria-labelledby':'mw-news-title'
      });
      // Canvas-Bühne (Whisky-Glas)
      var $canvasWrap = $('<div>', {'class':'mw-fw-canvas-wrap'});
      var $canvas = $('<canvas>', {'class':'mw-fw-canvas','aria-hidden':'true'});
      $canvasWrap.append($canvas);
      // Titel + Einleitung
      var $title = $('<h2>', { id:'mw-news-title' }).text(CONFIG.title);
      var $intro = $('<div>', {'class':'mw-popup-content'}).html(CONFIG.introHTML);
      // Bildkarten
      var $cards = $('<div>', {'class':'mw-wnews-cards'});
      CONFIG.images.forEach(function(img){
        var $card = $('<a>', {
          'class':'mw-wnews-card',
          'href': img.link,
          'target':'_blank',
          'rel':'noopener'
        });
        $card.append(
          $('<div>', {'class':'mw-wnews-thumb'}).append(
            $('<img>', { src: img.src, alt: img.alt, loading:'lazy' })
          ),
          $('<div>', {'class':'mw-wnews-meta'}).append(
            $('<div>', {'class':'mw-wnews-title', 'text': img.alt}),
            $('<div>', {'class':'mw-wnews-cta', 'text': img.cta || 'Mehr ansehen'})
          )
        );
        $cards.append($card);
      });
      // Buttons
      var $btnRow = $('<div>', {'class':'mw-popup-button-row'});
      var $ok = $('<button>', {'class':'mw-popup-close', type:'button'}).text('OK');
      $btnRow.append($ok);
      // Zusammenbauen
      $modal.append($canvasWrap, $title, $intro, $cards, $btnRow);
      $('body').append($overlay, $modal);
      // Schließen
      function close(){
        stopAnim();
        markSeen();
        $overlay.remove(); $modal.remove();
        $(document).off('keydown.mwwnews');
      }
      if (CONFIG.clickBackdropToClose) $overlay.on('click', close);
      $ok.on('click', close);
      if (CONFIG.escToClose) {
        $(document).on('keydown.mwwnews', function(e){
          var k = e.key || e.keyCode;
          if (k==='Escape' || k==='Esc' || k===27){ e.preventDefault(); close(); }
        });
      }
      // ==== Whisky-Animation (Lowball-Glas) ====
      var canvas = $canvas[0], ctx = canvas.getContext('2d');
      var dpr = Math.max(1, window.devicePixelRatio || 1);
      var w=0,h=0, raf=null, t0=0, reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      function resize(){
        var r = $canvasWrap[0].getBoundingClientRect();
        w = Math.floor(r.width * dpr);
        h = Math.floor(r.height * dpr);
        canvas.width = w; canvas.height = h;
        canvas.style.width = r.width+'px';
        canvas.style.height = r.height+'px';
      }
      function drawGlass(){
        // Glas-Form
        var gx = w*0.18, gy = h*0.15, gw = w*0.64, gh = h*0.7, radius = Math.min(gw,gh)*0.08;
        ctx.save();
        ctx.globalAlpha = 1;
        // Rand
        ctx.strokeStyle = 'rgba(255,255,255,0.3)';
        ctx.lineWidth = Math.max(2, 2*dpr);
        ctx.beginPath();
        ctx.moveTo(gx+radius, gy);
        ctx.arcTo(gx+gw, gy, gx+gw, gy+gh, radius);
        ctx.arcTo(gx+gw, gy+gh, gx, gy+gh, radius);
        ctx.arcTo(gx, gy+gh, gx, gy, radius);
        ctx.arcTo(gx, gy, gx+gw, gy, radius);
        ctx.closePath();
        ctx.stroke();
        // Glanz
        ctx.beginPath();
        ctx.moveTo(gx+gw*0.15, gy+gh*0.1);
        ctx.quadraticCurveTo(gx+gw*0.25, gy+gh*0.05, gx+gw*0.3, gy+gh*0.3);
        ctx.strokeStyle = 'rgba(255,255,255,0.18)';
        ctx.stroke();
        ctx.restore();
        return {x:gx,y:gy,w:gw,h:gh};
      }
      var bubbles = [];
      function resetBubble(b, liquidTop, glass){
        b.x = glass.x + Math.random()*glass.w*0.96 + glass.w*0.02;
        b.y = glass.y + glass.h - Math.random()*10*dpr;
        b.r = Math.random()*1.8*dpr + 0.8*dpr;
        b.speed = (Math.random()*0.2 + 0.1)*dpr;
        b.wobble = Math.random()*0.8 + 0.4;
        b.hue = 30 + Math.random()*20; // bernsteinfarben
        b.lTop = liquidTop;
      }
      function drawFrame(ts){
        if (!t0) t0 = ts;
        var t = (ts - t0)/1000;
        ctx.globalCompositeOperation = 'source-over';
        ctx.fillStyle = 'rgba(5,10,20,0.15)'; // leicht abdunkeln für Trail
        ctx.fillRect(0,0,w,h);
        // Glas
        var glass = drawGlass();
        // Flüssigkeit: Wellenhöhe
        var base = glass.y + glass.h*0.58;
        var amp = Math.min(14*dpr, h*0.03);
        var wave = Math.sin(t*2.2)*amp;
        var liquidTop = base + wave;
        // Whisky-Füllung
        var grd = ctx.createLinearGradient(0, liquidTop-30*dpr, 0, glass.y+glass.h);
        grd.addColorStop(0, 'rgba(255,190,90,0.95)');
        grd.addColorStop(1, 'rgba(170,85,20,0.98)');
        ctx.save();
        ctx.beginPath();
        // Clip in Glas
        var radius = Math.min(glass.w,glass.h)*0.08;
        ctx.moveTo(glass.x+radius, glass.y);
        ctx.arcTo(glass.x+glass.w, glass.y, glass.x+glass.w, glass.y+glass.h, radius);
        ctx.arcTo(glass.x+glass.w, glass.y+glass.h, glass.x, glass.y+glass.h, radius);
        ctx.arcTo(glass.x, glass.y+glass.h, glass.x, glass.y, radius);
        ctx.arcTo(glass.x, glass.y, glass.x+glass.w, glass.y, radius);
        ctx.closePath();
        ctx.clip();
        // Flüssigkeit füllen
        ctx.fillStyle = grd;
        ctx.fillRect(glass.x, liquidTop, glass.w, glass.y+glass.h - liquidTop);
        // Wellenlinie
        ctx.beginPath();
        ctx.moveTo(glass.x, liquidTop);
        for (var x=0; x<=glass.w; x+=6*dpr){
          var y = liquidTop + Math.sin((x*0.05) + t*3.2)*amp*0.2;
          ctx.lineTo(glass.x + x, y);
        }
        ctx.lineTo(glass.x+glass.w, glass.y+glass.h);
        ctx.lineTo(glass.x, glass.y+glass.h);
        ctx.closePath();
        ctx.fillStyle = 'rgba(255,165,70,0.3)';
        ctx.fill();
        // Bläschen
        if (bubbles.length === 0){
          for (var i=0;i<120;i++){ bubbles.push({}); resetBubble(bubbles[i], liquidTop, glass); }
        }
        ctx.globalCompositeOperation = 'lighter';
        bubbles.forEach(function(b){
          // nur in der Flüssigkeit zeichnen
          if (b.y < liquidTop + 3*dpr) {
            // neu starten, wenn Oberfläche erreicht
            resetBubble(b, liquidTop, glass);
          } else {
            // Auftrieb + leichtes Wabern
            b.y -= b.speed * (1 + Math.sin(t*3 + b.x*0.02)*0.2);
            b.x += Math.sin(t*2 + b.y*0.02)*b.wobble*0.15;
          }
          ctx.beginPath();
          ctx.fillStyle = 'rgba(255,220,140,0.9)';
          ctx.arc(b.x, b.y, b.r, 0, Math.PI*2);
          ctx.fill();
          // Glanzpunkt
          ctx.beginPath();
          ctx.fillStyle = 'rgba(255,255,255,0.6)';
          ctx.arc(b.x - b.r*0.3, b.y - b.r*0.3, b.r*0.35, 0, Math.PI*2);
          ctx.fill();
        });
        ctx.restore(); // Clip beenden
        // Kleine Highlights am Glasrand
        ctx.save();
        ctx.globalCompositeOperation = 'lighter';
        ctx.strokeStyle = 'rgba(255,255,255,0.15)';
        ctx.lineWidth = 1*dpr;
        ctx.beginPath();
        ctx.arc(glass.x + glass.w*0.85, glass.y + glass.h*0.15, 10*dpr, 0, Math.PI*2);
        ctx.stroke();
        ctx.restore();
        raf = requestAnimationFrame(drawFrame);
      }
      function startAnim(){
        if (reduce) return;
        resize();
        t0 = 0;
        raf = requestAnimationFrame(drawFrame);
        window.addEventListener('resize', resize);
      }
      function stopAnim(){
        if (raf){ cancelAnimationFrame(raf); raf=null; }
        window.removeEventListener('resize', resize);
      }
      startAnim();
      markSeen();
    });
  })(jQuery, mw);
});
});