MediaWiki:Common.js: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 739: Zeile 739:
// -------------------------------------
// -------------------------------------


/* Whisky News – World of Whisky (Mannheim) – Popup (v3, Wave-Fix)
/* Whisky News – Popup (v4) – Einschenk-Animation im Glas */
  - Flüssigkeit wird als Wellen-Path gefüllt (keine harte Kante mehr)
  - HiDPI & ResizeObserver wie gehabt
*/
mw.loader.using(['mediawiki.util','jquery']).then(function(){
mw.loader.using(['mediawiki.util','jquery']).then(function(){
   (function($, mw){
   (function($, mw){
Zeile 749: Zeile 746:
     var CONFIG = {
     var CONFIG = {
       enabled: true,
       enabled: true,
       id: 'wow_mannheim_whisky_news_v3',
       id: 'wow_mannheim_whisky_news_v4', // Version hochsetzen
       title: 'Whisky News: Messeabfüllungen – World of Whisky (Mannheim)',
       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>',
       introHTML: '<p>Frisch zur Messe in Mannheim: Zwei limitierte Abfüllungen. Schau sie dir an und bewerte sie im Wiki!</p>',
       images: [
       images: [
         {
         {
           src: 'https://ados-wiki.de/images/2/2f/South_Islay_Single_Malt_13_year-old_%28Sherry_Octave_Cask_Finish%29.single.jpg', // ← deine finale URL
          // >>> Bild-URL ersetzen
           src: 'https://ados-wiki.de/images/2/2f/South_Islay_Single_Malt_13_year-old_%28Sherry_Octave_Cask_Finish%29.single.jpg',
           alt: 'South Islay 13y – Sherry Octave Cask Finish',
           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)',
           link: 'https://ados-wiki.de/wiki/South_Islay_Single_Malt_13_year-old_(Sherry_Octave_Cask_Finish)',
Zeile 760: Zeile 758:
         },
         },
         {
         {
           src: 'https://ados-wiki.de/images/9/95/Tullibardine_13_year-old_%28Shiraz_Wine_Octave_Cask_Finish%29.single.jpg', // ← deine finale URL
          // >>> Bild-URL ersetzen
           src: 'https://ados-wiki.de/images/9/95/Tullibardine_13_year-old_%28Shiraz_Wine_Octave_Cask_Finish%29.single.jpg',
           alt: 'Tullibardine 13y – Shiraz Wine Octave Cask Finish',
           alt: 'Tullibardine 13y – Shiraz Wine Octave Cask Finish',
           link: 'https://ados-wiki.de/wiki/Tullibardine_13_year-old',
           link: 'https://ados-wiki.de/wiki/Tullibardine_13_year-old',
Zeile 768: Zeile 767:
       showOnNamespaces: 'all',
       showOnNamespaces: 'all',
       escToClose: true,
       escToClose: true,
       clickBackdropToClose: true
       clickBackdropToClose: true,
 
      // Animation-Parameter
      targetFillPct: 0.60,      // finaler Füllstand relativ zur Glas-Höhe
      pourDurationMs: 2600,    // Einschenk-Dauer
      splashCount: 90          // Anzahl Spritzer/Tröpfchen beim Eintreffen
     };
     };


Zeile 788: Zeile 792:


     $(function(){
     $(function(){
      // Grundgerüst
       var $overlay = $('<div>', {'class':'mw-popup-overlay'});
       var $overlay = $('<div>', {'class':'mw-popup-overlay'});
       var $modal = $('<div>', {'class':'mw-popup-modal','role':'dialog','aria-modal':'true','aria-labelledby':'mw-news-title'});
       var $modal = $('<div>', {'class':'mw-popup-modal','role':'dialog','aria-modal':'true','aria-labelledby':'mw-news-title'});
Zeile 833: Zeile 838:
       }
       }


       // ===== Canvas-Animation (Wave-Fix) =====
       // ===== Canvas – Einschenken =====
       var canvas = $canvas[0], ctx = canvas.getContext && canvas.getContext('2d');
       var canvas = $canvas[0], ctx = canvas.getContext && canvas.getContext('2d');
       var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
       var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
       var dpr=1, cw=0, ch=0, raf=null, t0=0, started=false, bubbles=[], ro;
       var dpr=1, cw=0, ch=0, raf=null, started=false, ro, t0=0;
 
      // Glas-Geometrie (in Device-Pixeln)
      var glass = {x:0,y:0,w:0,h:0,r:0};
 
      // Flüssigkeit & Effekte
      var fillTopY;              // aktuelle Oberfläche (y in px)
      var targetTopY;            // Zieloberfläche
      var ampBase=0, amp=0;      // Wellenamplitude
      var bubbles=[], splashes=[];
      var pouring=true;


       function setSize(){
       function setSize(){
Zeile 848: Zeile 863:
           canvas.style.width = cw+'px';
           canvas.style.width = cw+'px';
           canvas.style.height = ch+'px';
           canvas.style.height = ch+'px';
          setupGlass();
         }
         }
         return true;
         return true;
      }
      function setupGlass(){
        var w = canvas.width, h = canvas.height;
        glass.x = w*0.18; glass.y = h*0.15; glass.w = w*0.64; glass.h = h*0.7;
        glass.r = Math.min(glass.w,glass.h)*0.08;
        // Start: leer – Oberfläche am Boden
        var baseEmpty = glass.y + glass.h*0.98;
        fillTopY = baseEmpty;
        targetTopY = glass.y + glass.h*(1 - CONFIG.targetFillPct); // je niedriger Y, desto höher Füllung
        ampBase = Math.min(14*dpr, canvas.height*0.03);
        amp = 0; // steigert sich während des Einschenkens
        bubbles = [];
        splashes = [];
       }
       }


       function drawGlass(){
       function drawGlass(){
        var w = canvas.width, h = canvas.height;
        var gx = w*0.18, gy = h*0.15, gw = w*0.64, gh = h*0.7, r = Math.min(gw,gh)*0.08;
         ctx.save();
         ctx.save();
         ctx.strokeStyle = 'rgba(255,255,255,0.28)';
         ctx.strokeStyle = 'rgba(255,255,255,0.28)';
         ctx.lineWidth = Math.max(2, 2*dpr);
         ctx.lineWidth = Math.max(2, 2*dpr);
         ctx.beginPath();
         ctx.beginPath();
         ctx.moveTo(gx+r, gy);
         ctx.moveTo(glass.x+glass.r, glass.y);
         ctx.arcTo(gx+gw, gy, gx+gw, gy+gh, r);
         ctx.arcTo(glass.x+glass.w, glass.y, glass.x+glass.w, glass.y+glass.h, glass.r);
         ctx.arcTo(gx+gw, gy+gh, gx, gy+gh, r);
         ctx.arcTo(glass.x+glass.w, glass.y+glass.h, glass.x, glass.y+glass.h, glass.r);
         ctx.arcTo(gx, gy+gh, gx, gy, r);
         ctx.arcTo(glass.x, glass.y+glass.h, glass.x, glass.y, glass.r);
         ctx.arcTo(gx, gy, gx+gw, gy, r);
         ctx.arcTo(glass.x, glass.y, glass.x+glass.w, glass.y, glass.r);
         ctx.closePath();
         ctx.closePath();
         ctx.stroke();
         ctx.stroke();
         // kleiner Glanz
 
         // Glanz
         ctx.beginPath();
         ctx.beginPath();
         ctx.moveTo(gx+gw*0.15, gy+gh*0.1);
         ctx.moveTo(glass.x+glass.w*0.15, glass.y+glass.h*0.1);
         ctx.quadraticCurveTo(gx+gw*0.25, gy+gh*0.05, gx+gw*0.3, gy+gh*0.3);
         ctx.quadraticCurveTo(glass.x+glass.w*0.25, glass.y+glass.h*0.05, glass.x+glass.w*0.3, glass.y+glass.h*0.3);
         ctx.strokeStyle = 'rgba(255,255,255,0.18)';
         ctx.strokeStyle = 'rgba(255,255,255,0.18)';
         ctx.stroke();
         ctx.stroke();
         ctx.restore();
         ctx.restore();
        return {x:gx,y:gy,w:gw,h:gh,r:r};
       }
       }


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


      function resetBubble(b, liquidTop, glass){
       function drawLiquid(t){
        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;
      }
 
       function drawLiquid(glass, t){
        var base = glass.y + glass.h*0.58;              // mittlere Füllhöhe
        var amp  = Math.min(14*dpr, canvas.height*0.03);// Amplitude
        var wave = Math.sin(t*2.2)*amp;
        var topY = base + wave;
 
         // Farbverlauf
         // Farbverlauf
         var grd = ctx.createLinearGradient(0, topY-30*dpr, 0, glass.y+glass.h);
         var grd = ctx.createLinearGradient(0, fillTopY-30*dpr, 0, glass.y+glass.h);
         grd.addColorStop(0, 'rgba(255,190,90,0.96)');
         grd.addColorStop(0, 'rgba(255,190,90,0.96)');
         grd.addColorStop(1, 'rgba(170,85,20,0.98)');
         grd.addColorStop(1, 'rgba(170,85,20,0.98)');
Zeile 908: Zeile 923:
         ctx.clip();
         ctx.clip();


         // === Wellen-PATH als komplette Flüssigkeit (kein Rechteck!) ===
         // Wellenoberfläche als kompletter Path
        var topY = fillTopY + Math.sin(t*3.2)*amp*0.25;
         ctx.beginPath();
         ctx.beginPath();
         ctx.moveTo(glass.x, topY);
         ctx.moveTo(glass.x, topY);
         for (var x=0; x<=glass.w; x+=6*dpr){
         for (var x=0; x<=glass.w; x+=6*dpr){
           var y = topY + Math.sin((x*0.05) + t*3.2) * amp * 0.22;
           var y = topY + Math.sin((x*0.055) + t*4.0)*amp*0.22;
           ctx.lineTo(glass.x + x, y);
           ctx.lineTo(glass.x + x, y);
         }
         }
        // Seiten + Boden schließen
         ctx.lineTo(glass.x + glass.w, glass.y + glass.h);
         ctx.lineTo(glass.x + glass.w, glass.y + glass.h);
         ctx.lineTo(glass.x,             glass.y + glass.h);
         ctx.lineTo(glass.x,             glass.y + glass.h);
         ctx.closePath();
         ctx.closePath();
         ctx.fillStyle = grd;
         ctx.fillStyle = grd;
         ctx.fill();
         ctx.fill();


         // dezente helle Gischt auf dem Kamm
         // dezente Gischt
         ctx.beginPath();
         ctx.beginPath();
         ctx.moveTo(glass.x, topY);
         ctx.moveTo(glass.x, topY);
         for (var x2=0; x2<=glass.w; x2+=6*dpr){
         for (var x2=0; x2<=glass.w; x2+=6*dpr){
           var y2 = topY + Math.sin((x2*0.05) + t*3.2) * amp * 0.22;
           var y2 = topY + Math.sin((x2*0.055) + t*4.0)*amp*0.22;
           ctx.lineTo(glass.x + x2, y2);
           ctx.lineTo(glass.x + x2, y2);
         }
         }
Zeile 933: Zeile 948:
         ctx.stroke();
         ctx.stroke();


         // Blasen (nur innerhalb der Flüssigkeit)
         // Spritzer (nur während Einschenken stärker)
        for (var i=splashes.length-1; i>=0; i--){
          var sp = splashes[i];
          sp.vy += 0.12*dpr;  // Gravitation
          sp.x += sp.vx;
          sp.y += sp.vy;
          sp.life -= 1;
          ctx.beginPath();
          ctx.fillStyle = 'rgba(255,220,140,' + Math.max(0, sp.life/60).toFixed(2) + ')';
          ctx.arc(sp.x, sp.y, sp.r, 0, Math.PI*2);
          ctx.fill();
          if (sp.life <= 0 || sp.y > glass.y+glass.h) splashes.splice(i,1);
        }
 
        // Blasen/Lichtpunkte
         if (bubbles.length === 0){
         if (bubbles.length === 0){
           for (var i=0;i<120;i++){ bubbles.push({}); resetBubble(bubbles[i], topY, glass); }
           for (var j=0;j<130;j++){
            bubbles.push({ x: glass.x + Math.random()*glass.w, y: glass.y + glass.h - rand(0,8*dpr), r: rand(0.8,2.2)*dpr, s: rand(0.1,0.3)*dpr, wob: rand(0.3,0.9) });
          }
         }
         }
         ctx.globalCompositeOperation = 'lighter';
         ctx.globalCompositeOperation = 'lighter';
         bubbles.forEach(function(b){
         bubbles.forEach(function(b){
           if (b.y < topY + 3*dpr) resetBubble(b, topY, glass);
           if (b.y < topY + 3*dpr){
           else {
            // neu starten
             b.y -= b.speed * (1 + Math.sin(t*3 + b.x*0.02)*0.2);
            b.x = glass.x + Math.random()*glass.w;
             b.x += Math.sin(t*2 + b.y*0.02)*b.wobble*0.15;
            b.y = glass.y + glass.h - rand(0,8*dpr);
           } else {
             b.y -= b.s * (1 + Math.sin(t*3 + b.x*0.02)*0.2);
             b.x += Math.sin(t*2 + b.y*0.02)*b.wob*0.15;
           }
           }
           ctx.beginPath();
           ctx.beginPath();
Zeile 948: Zeile 982:
           ctx.arc(b.x, b.y, b.r, 0, Math.PI*2);
           ctx.arc(b.x, b.y, b.r, 0, Math.PI*2);
           ctx.fill();
           ctx.fill();
 
          // Glanz
           ctx.beginPath();
           ctx.beginPath();
           ctx.fillStyle = 'rgba(255,255,255,0.6)';
           ctx.fillStyle = 'rgba(255,255,255,0.6)';
Zeile 955: Zeile 989:
         });
         });


         ctx.restore(); // Clip
         ctx.restore(); // Clip Ende
      }
 
      function drawPourStream(t){
        // Einschenk-Strahl über dem Glas
        var streamX = glass.x + glass.w*0.6; // leicht rechts
        var startY  = Math.max(0, glass.y - 0.25*canvas.height);
        var endY    = fillTopY - 6*dpr;
        ctx.save();
        ctx.globalAlpha = 0.95;
        var grad = ctx.createLinearGradient(0, startY, 0, endY);
        grad.addColorStop(0, 'rgba(255,200,120,0.5)');
        grad.addColorStop(1, 'rgba(200,110,25,0.9)');
        ctx.strokeStyle = grad;
        ctx.lineWidth = Math.max(3*dpr, 3);
        ctx.lineCap = 'round';
 
        // leicht wabernde Kurve
        ctx.beginPath();
        ctx.moveTo(streamX, startY);
        var midY = (startY + endY)/2;
        var offset = Math.sin(t*6)*10*dpr;
        ctx.quadraticCurveTo(streamX + offset, midY, streamX, endY);
        ctx.stroke();
        ctx.restore();
 
        // Einschlag-Splash erzeugen
        if (Math.random() < 0.35 && splashes.length < CONFIG.splashCount){
          var angle = rand(-Math.PI*0.8, -Math.PI*0.2);
          var speed = rand(3*dpr, 6*dpr);
          splashes.push({
            x: streamX,
            y: endY,
            vx: Math.cos(angle)*speed,
            vy: Math.sin(angle)*speed*0.6,
            r: rand(0.8, 2.0)*dpr,
            life: 60
          });
        }
       }
       }


Zeile 962: Zeile 1.034:
         var t = (ts - t0)/1000;
         var t = (ts - t0)/1000;


         // weiches Redraw
         // Hintergrund
         ctx.globalCompositeOperation = 'source-over';
         ctx.globalCompositeOperation = 'source-over';
         ctx.fillStyle = 'rgba(5,10,20,0.16)';
         ctx.fillStyle = 'rgba(5,10,20,0.2)';
         ctx.fillRect(0,0,canvas.width,canvas.height);
         ctx.fillRect(0,0,canvas.width,canvas.height);


         var glass = drawGlass();
         drawGlass();
         drawLiquid(glass, t);
 
        // Einschenk-Phase: Füllstand anheben
        if (pouring){
          var progress = Math.min(1, (ts - t0) / CONFIG.pourDurationMs);
          // easeOutCubic
          var eased = 1 - Math.pow(1 - progress, 3);
          fillTopY = (glass.y + glass.h*0.98) + (targetTopY - (glass.y + glass.h*0.98)) * eased;
          amp = ampBase * (0.3 + 0.7*eased);
          drawPourStream(t);
          if (progress >= 1) {
            pouring = false;
            // nach dem Einschenken: Wellen bauen langsam ab
          }
        } else {
          // langsames Abklingen der Wellen
          amp = Math.max(amp * 0.985, ampBase*0.25);
        }
 
         drawLiquid(t);


         // kleiner Randglanz
         // Glasrand-Glanz
         ctx.save();
         ctx.save();
         ctx.globalCompositeOperation = 'lighter';
         ctx.globalCompositeOperation = 'lighter';
Zeile 984: Zeile 1.074:


       function startAnim(){
       function startAnim(){
         if (!ctx || (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches)) return;
         if (!ctx || reduce) return;
         if (!setSize()) { setTimeout(startAnim, 50); return; }
         if (!setSize()) { setTimeout(startAnim, 50); return; }
         if (started) return;
         if (started) return;
         started = true; t0 = 0;
         started = true; t0 = 0; pouring = true;
         raf = requestAnimationFrame(frame);
         raf = requestAnimationFrame(frame);


         if ('ResizeObserver' in window) {
         if ('ResizeObserver' in window) {
           ro = new ResizeObserver(setSize);
           ro = new ResizeObserver(function(){ setSize(); });
           ro.observe($stage[0]);
           ro.observe($stage[0]);
         } else {
         } else {