MediaWiki:Common.js: Unterschied zwischen den Versionen

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


/* Whisky News – Popup (v4) – Einschenk-Animation im Glas */
/* Whisky News – Popup (v5)
  - Behält News (Titel + 2 Karten) bei
  - Oben Canvas mit einschenkendem Whiskyglas
  - „Slàinte mhath“ Text-Overlay (fade-in/out)
*/
mw.loader.using(['mediawiki.util','jquery']).then(function(){
mw.loader.using(['mediawiki.util','jquery']).then(function(){
   (function($, mw){
   (function($, mw){
Zeile 746: Zeile 750:
     var CONFIG = {
     var CONFIG = {
       enabled: true,
       enabled: true,
       id: 'wow_mannheim_whisky_news_v4', // Version hochsetzen
       id: 'wow_mannheim_whisky_news_v5', // Version hochsetzen, damit alle es sehen
       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>',
      // <<< Bild-URLs bitte mit euren finalen Datei-Links ersetzen >>>
       images: [
       images: [
         {
         {
          // >>> 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',
           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',
Zeile 758: Zeile 763:
         },
         },
         {
         {
          // >>> Bild-URL ersetzen
           src: 'https://ados-wiki.de/images/9/95/Tullibardine_13_year-old_%28Shiraz_Wine_Octave_Cask_Finish%29.single.jpg',
           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',
Zeile 765: Zeile 769:
         }
         }
       ],
       ],
       showOnNamespaces: 'all',
 
       // Popup-Verhalten
       escToClose: true,
       escToClose: true,
       clickBackdropToClose: true,
       clickBackdropToClose: true,
      dailyLimit: 1,


       // Animation-Parameter
       // Animation
       targetFillPct: 0.60,     // finaler Füllstand relativ zur Glas-Höhe
       targetFillPct: 0.60,   // finaler Füllstand
       pourDurationMs: 2600,     // Einschenk-Dauer
       pourDurationMs: 2600,   // Einschenk-Dauer
       splashCount: 90           // Anzahl Spritzer/Tröpfchen beim Eintreffen
       splashCount: 90,        // Spritzer
      slainteText: 'Slàinte mhath',
      slainteShowMs: 2600,    // so lange sichtbar
      slainteFadeMs: 800      // ausfaden
     };
     };


     if (!CONFIG.enabled) return;
     if (!CONFIG.enabled) return;
    var ns = mw.config.get('wgNamespaceNumber');
    if (CONFIG.showOnNamespaces !== 'all' &&
        $.isArray(CONFIG.showOnNamespaces) &&
        $.inArray(ns, CONFIG.showOnNamespaces) === -1) return;


     // 1×/Tag
     // 1×/Tag
Zeile 838: Zeile 842:
       }
       }


       // ===== Canvas – Einschenken =====
       // ===== Canvas – Einschenken + „Slàinte mhath“ =====
       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, started=false, ro, t0=0;
       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};
       var glass = {x:0,y:0,w:0,h:0,r:0};
 
       var fillTopY, targetTopY, ampBase=0, amp=0, bubbles=[], splashes=[];
      // Flüssigkeit & Effekte
       var pouring=true, slainteStart=0;
       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 872: Zeile 870:
         glass.x = w*0.18; glass.y = h*0.15; glass.w = w*0.64; glass.h = h*0.7;
         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;
         glass.r = Math.min(glass.w,glass.h)*0.08;
        // Start: leer – Oberfläche am Boden
         var baseEmpty = glass.y + glass.h*0.98;
         var baseEmpty = glass.y + glass.h*0.98;
         fillTopY = baseEmpty;
         fillTopY = baseEmpty;
         targetTopY = glass.y + glass.h*(1 - CONFIG.targetFillPct); // je niedriger Y, desto höher Füllung
         targetTopY = glass.y + glass.h*(1 - CONFIG.targetFillPct);
         ampBase = Math.min(14*dpr, canvas.height*0.03);
         ampBase = Math.min(14*dpr, canvas.height*0.03);
         amp = 0; // steigert sich während des Einschenkens
         amp = 0; bubbles=[]; splashes=[];
        bubbles = [];
        splashes = [];
       }
       }


Zeile 894: Zeile 889:
         ctx.closePath();
         ctx.closePath();
         ctx.stroke();
         ctx.stroke();
         // Glanz
         // Glanz
         ctx.beginPath();
         ctx.beginPath();
Zeile 907: Zeile 901:


       function drawLiquid(t){
       function drawLiquid(t){
        // Farbverlauf
         var grd = ctx.createLinearGradient(0, fillTopY-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)');
Zeile 923: Zeile 916:
         ctx.clip();
         ctx.clip();


         // Wellenoberfläche als kompletter Path
         // Wellenoberfläche als Path
         var topY = fillTopY + Math.sin(t*3.2)*amp*0.25;
         var topY = fillTopY + Math.sin(t*3.2)*amp*0.25;
         ctx.beginPath();
         ctx.beginPath();
Zeile 937: Zeile 930:
         ctx.fill();
         ctx.fill();


         // dezente Gischt
         // helle Gischt
         ctx.beginPath();
         ctx.beginPath();
         ctx.moveTo(glass.x, topY);
         ctx.moveTo(glass.x, topY);
Zeile 947: Zeile 940:
         ctx.lineWidth = Math.max(1, 1*dpr);
         ctx.lineWidth = Math.max(1, 1*dpr);
         ctx.stroke();
         ctx.stroke();
        // 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
         // Blasen/Lichtpunkte
Zeile 971: Zeile 950:
         bubbles.forEach(function(b){
         bubbles.forEach(function(b){
           if (b.y < topY + 3*dpr){
           if (b.y < topY + 3*dpr){
            // neu starten
             b.x = glass.x + Math.random()*glass.w;
             b.x = glass.x + Math.random()*glass.w;
             b.y = glass.y + glass.h - rand(0,8*dpr);
             b.y = glass.y + glass.h - rand(0,8*dpr);
Zeile 982: Zeile 960:
           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 989: Zeile 966:
         });
         });


         ctx.restore(); // Clip Ende
         ctx.restore();
       }
       }


       function drawPourStream(t){
       function drawPourStream(t){
        // Einschenk-Strahl über dem Glas
         var streamX = glass.x + glass.w*0.6;
         var streamX = glass.x + glass.w*0.6; // leicht rechts
         var startY  = Math.max(0, glass.y - 0.25*canvas.height);
         var startY  = Math.max(0, glass.y - 0.25*canvas.height);
         var endY    = fillTopY - 6*dpr;
         var endY    = fillTopY - 6*dpr;
Zeile 1.005: Zeile 981:
         ctx.lineWidth = Math.max(3*dpr, 3);
         ctx.lineWidth = Math.max(3*dpr, 3);
         ctx.lineCap = 'round';
         ctx.lineCap = 'round';
        // leicht wabernde Kurve
         ctx.beginPath();
         ctx.beginPath();
         ctx.moveTo(streamX, startY);
         ctx.moveTo(streamX, startY);
Zeile 1.015: Zeile 989:
         ctx.restore();
         ctx.restore();


         // Einschlag-Splash erzeugen
         // Spritzer
         if (Math.random() < 0.35 && splashes.length < CONFIG.splashCount){
         if (Math.random() < 0.35 && splashes.length < CONFIG.splashCount){
           var angle = rand(-Math.PI*0.8, -Math.PI*0.2);
           var angle = rand(-Math.PI*0.8, -Math.PI*0.2);
           var speed = rand(3*dpr, 6*dpr);
           var speed = rand(3*dpr, 6*dpr);
           splashes.push({
           splashes.push({
             x: streamX,
             x: streamX, y: endY, vx: Math.cos(angle)*speed, vy: Math.sin(angle)*speed*0.6,
            y: endY,
             r: rand(0.8, 2.0)*dpr, life: 60
            vx: Math.cos(angle)*speed,
            vy: Math.sin(angle)*speed*0.6,
             r: rand(0.8, 2.0)*dpr,
            life: 60
           });
           });
        }
        for (var i=splashes.length-1; i>=0; i--){
          var sp = splashes[i];
          sp.vy += 0.12*dpr; 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);
         }
         }
      }
      function drawSlainte(now){
        // Zeit seit Start der Animation
        var elapsed = now - slainteStart;
        var alpha = 0;
        if (elapsed < CONFIG.slainteShowMs) {
          // Einblenden in den ersten 500ms
          alpha = Math.min(1, elapsed/500);
        } else {
          // Ausblenden
          var out = elapsed - CONFIG.slainteShowMs;
          alpha = Math.max(0, 1 - out/CONFIG.slainteFadeMs);
        }
        if (alpha <= 0) return;
        ctx.save();
        ctx.globalAlpha = alpha;
        ctx.fillStyle = '#fff';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        // große Schrift zentriert über der Flüssigkeit
        var fontSize = Math.max(24, Math.floor(canvas.height * 0.12));
        ctx.font = '600 ' + fontSize + 'px "Segoe UI", Arial, sans-serif';
        ctx.shadowColor = 'rgba(255,200,120,0.6)';
        ctx.shadowBlur = 18;
        ctx.fillText(CONFIG.slainteText, canvas.width/2, glass.y + glass.h*0.28);
        ctx.restore();
       }
       }


       function frame(ts){
       function frame(ts){
         if (!t0) t0 = ts;
         if (!t0) { t0 = ts; slainteStart = ts; }
         var t = (ts - t0)/1000;
         var t = (ts - t0)/1000;


Zeile 1.041: Zeile 1.049:
         drawGlass();
         drawGlass();


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


         drawLiquid(t);
         drawLiquid(t);
        drawSlainte(ts);


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


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