MediaWiki:Common.js: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 739: | Zeile 739: | ||
// ------------------------------------- | // ------------------------------------- | ||
/* Whisky News – | /* Whisky News – Popup (v4) – Einschenk-Animation im Glas */ | ||
*/ | |||
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: ' | 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', | // >>> 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', | // >>> 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 | // ===== 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, | 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(){ | ||
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( | ctx.moveTo(glass.x+glass.r, glass.y); | ||
ctx.arcTo( | ctx.arcTo(glass.x+glass.w, glass.y, glass.x+glass.w, glass.y+glass.h, glass.r); | ||
ctx.arcTo( | ctx.arcTo(glass.x+glass.w, glass.y+glass.h, glass.x, glass.y+glass.h, glass.r); | ||
ctx.arcTo( | ctx.arcTo(glass.x, glass.y+glass.h, glass.x, glass.y, glass.r); | ||
ctx.arcTo( | ctx.arcTo(glass.x, glass.y, glass.x+glass.w, glass.y, glass.r); | ||
ctx.closePath(); | ctx.closePath(); | ||
ctx.stroke(); | ctx.stroke(); | ||
// | |||
// Glanz | |||
ctx.beginPath(); | ctx.beginPath(); | ||
ctx.moveTo( | ctx.moveTo(glass.x+glass.w*0.15, glass.y+glass.h*0.1); | ||
ctx.quadraticCurveTo( | 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(); | ||
} | } | ||
function rand(min,max){ return Math.random()*(max-min)+min; } | function rand(min,max){ return Math.random()*(max-min)+min; } | ||
function drawLiquid(t){ | |||
function drawLiquid( | |||
// Farbverlauf | // Farbverlauf | ||
var grd = ctx.createLinearGradient(0, | 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(); | ||
// = | // 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. | 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); | ||
} | } | ||
ctx.lineTo(glass.x + glass.w, glass.y + glass.h); | ctx.lineTo(glass.x + glass.w, glass.y + glass.h); | ||
ctx.lineTo(glass.x, | ctx.lineTo(glass.x, glass.y + glass.h); | ||
ctx.closePath(); | ctx.closePath(); | ||
ctx.fillStyle = grd; | ctx.fillStyle = grd; | ||
ctx.fill(); | ctx.fill(); | ||
// dezente | // 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. | 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(); | ||
// | // 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 | 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) | if (b.y < topY + 3*dpr){ | ||
else { | // neu starten | ||
b.y -= b. | b.x = glass.x + Math.random()*glass.w; | ||
b.x += Math.sin(t*2 + b.y*0.02)*b. | 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; | ||
// | // Hintergrund | ||
ctx.globalCompositeOperation = 'source-over'; | ctx.globalCompositeOperation = 'source-over'; | ||
ctx.fillStyle = 'rgba(5,10,20,0. | 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(); | ||
drawLiquid( | |||
// 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); | |||
// | // Glasrand-Glanz | ||
ctx.save(); | ctx.save(); | ||
ctx.globalCompositeOperation = 'lighter'; | ctx.globalCompositeOperation = 'lighter'; | ||
| Zeile 984: | Zeile 1.074: | ||
function startAnim(){ | function startAnim(){ | ||
if (!ctx || | 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 { | ||