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 – Popup ( | /* 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: ' | 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: [ | ||
{ | { | ||
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: | ||
}, | }, | ||
{ | { | ||
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: | ||
} | } | ||
], | ], | ||
// Popup-Verhalten | |||
escToClose: true, | escToClose: true, | ||
clickBackdropToClose: true, | clickBackdropToClose: true, | ||
dailyLimit: 1, | |||
// Animation | // Animation | ||
targetFillPct: 0.60, | targetFillPct: 0.60, // finaler Füllstand | ||
pourDurationMs: 2600, | pourDurationMs: 2600, // Einschenk-Dauer | ||
splashCount: 90 | splashCount: 90, // Spritzer | ||
slainteText: 'Slàinte mhath', | |||
slainteShowMs: 2600, // so lange sichtbar | |||
slainteFadeMs: 800 // ausfaden | |||
}; | }; | ||
if (!CONFIG.enabled) return; | if (!CONFIG.enabled) 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; | ||
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=[]; | |||
var pouring=true, slainteStart=0; | |||
var fillTopY | |||
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; | ||
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); | 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; | amp = 0; 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){ | ||
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 | // 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(); | ||
// | // 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(); | ||
// 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){ | ||
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(); | ||
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(); | ctx.restore(); | ||
} | } | ||
function drawPourStream(t){ | function drawPourStream(t){ | ||
var streamX = glass.x + glass.w*0.6; | |||
var streamX = glass.x + glass.w*0.6; | |||
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'; | ||
ctx.beginPath(); | ctx.beginPath(); | ||
ctx.moveTo(streamX, startY); | ctx.moveTo(streamX, startY); | ||
| Zeile 1.015: | Zeile 989: | ||
ctx.restore(); | ctx.restore(); | ||
// | // 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, | ||
r: rand(0.8, 2.0)*dpr, life: 60 | |||
r: rand(0.8, 2.0)*dpr, | |||
}); | }); | ||
} | |||
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(); | ||
// | // Einschenken | ||
if (pouring){ | if (pouring){ | ||
var progress = Math.min(1, (ts - t0) / CONFIG.pourDurationMs); | var progress = Math.min(1, (ts - t0) / CONFIG.pourDurationMs); | ||
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 = 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; | ||
} else { | } else { | ||
amp = Math.max(amp * 0.985, ampBase*0.25); | amp = Math.max(amp * 0.985, ampBase*0.25); | ||
} | } | ||
drawLiquid(t); | drawLiquid(t); | ||
drawSlainte(ts); | |||
// | // 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); | ||