{"id":31924,"date":"2025-08-04T08:00:27","date_gmt":"2025-08-04T06:00:27","guid":{"rendered":"https:\/\/labs.ubn.one\/signage\/?page_id=31924"},"modified":"2025-12-02T13:38:54","modified_gmt":"2025-12-02T12:38:54","slug":"world-of-warcraft","status":"publish","type":"page","link":"https:\/\/labs.ubn.one\/signage\/index.php\/world-of-warcraft\/","title":{"rendered":"World of Warcraft"},"content":{"rendered":"\n<!-- LOADING SCREEN -->\n<style>\n  #loading-overlay {\n    position: fixed;\n    top: 0; left: 0;\n    width: 1920px;\n    height: 1080px;\n    z-index: 9999;\n    overflow: hidden;\n    background-size: cover;\n    background-position: center;\n    background-color: black;\n  }\n\n  #loading-bar-container {\n    position: absolute;\n    bottom: 71px;\n    left: 50%;\n    transform: translateX(-50%);\n    width: 625px;\n    height: 23px;\n    background: transparent;\n    overflow: hidden;\n  }\n\n  #loading-bar {\n    height: 100%;\n    width: 0%;\n    background-color: #255d6c;\n    transition: width 0.8s ease; \/* smoother jumps *\/\n  }\n\n  #loading-bar-frame {\n    position: absolute;\n    bottom: 50px;\n    left: 50%;\n    transform: translateX(-50%);\n    width: 984px;\n    height: 66px;\n    background: url(\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow_loadingbar.png\") no-repeat center center;\n    background-size: contain;\n    pointer-events: none;\n  }\n<\/style>\n\n<div id=\"loading-overlay\">\n  <div id=\"loading-bar-container\">\n    <div id=\"loading-bar\"><\/div>\n  <\/div>\n  <div id=\"loading-bar-frame\"><\/div>\n<\/div>\n\n<!-- Hidden audio element -->\n<audio id=\"loading-sound\" src=\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-quest.mp3\"><\/audio>\n\n<script>\ndocument.addEventListener(\"DOMContentLoaded\", function() {\n  \/\/ Config\n  const displaySeconds = 7;   \/\/ configurable\n  const debug = false;        \/\/ true = overlay stays visible for alignment\n  const images = [\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading01.jpeg\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading02.jpeg\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading03.png\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading04.jpg\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading05.webp\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading06.jpg\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading07.png\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading08.webp\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading09.png\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading10.png\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading11.jpg\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading12.jpg\",\n    \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-loading13.webp\"\n  ];\n\n  \/\/ Pick random background\n  const overlay = document.getElementById(\"loading-overlay\");\n  overlay.style.backgroundImage = `url(${images[Math.floor(Math.random()*images.length)]})`;\n\n  \/\/ --- SOUND FIX ---\n  const audio = document.getElementById(\"loading-sound\");\n  if (audio) {\n    audio.volume = 1.0;\n    audio.play().catch(() => {\n      console.warn(\"Autoplay blocked, waiting for user interaction...\");\n      const startAudio = () => {\n        audio.play().catch(err => console.warn(\"Still blocked:\", err));\n        document.removeEventListener(\"click\", startAudio);\n        document.removeEventListener(\"keydown\", startAudio);\n      };\n      document.addEventListener(\"click\", startAudio);\n      document.addEventListener(\"keydown\", startAudio);\n    });\n  }\n\n  \/\/ --- LOADING BAR JUMPS ---\n  const bar = document.getElementById(\"loading-bar\");\n\n  \/\/ Generate 2 or 3 random checkpoints\n  const jumps = Math.random() < 0.5 ? 2 : 3;\n  const checkpoints = [];\n  for (let i = 0; i < jumps; i++) {\n    checkpoints.push(Math.floor(Math.random() * 80)); \/\/ keep < 80% for realism\n  }\n  checkpoints.sort((a,b) => a - b);\n  checkpoints.push(100); \/\/ always end at 100%\n\n  let i = 0;\n  function doJump() {\n    if (i < checkpoints.length) {\n      bar.style.width = checkpoints[i] + \"%\";\n      i++;\n      if (i < checkpoints.length) {\n        \/\/ spread jumps evenly across displaySeconds\n        setTimeout(doJump, (displaySeconds * 1000) \/ checkpoints.length);\n      } else if (!debug) {\n        \/\/ hide overlay after last jump if not debugging\n        setTimeout(() => { overlay.style.display = \"none\"; }, 600);\n      }\n    }\n  }\n  doJump();\n});\n<\/script>\n\n\n\n<!-- RAIN AND SNOW EFFECT WITH BUTTON -->\n<script>\n\/\/ === WEATHER TEST MODE ===\nlet weatherTest = \"\"; \/\/ \"rain\", \"snow\", \"off\", or \"\" for live\n\n\/\/ === CONFIG ===\nconst WEATHER_API_KEY = \"e0be2d2da359cc045c4cecd70bc94e5a\";\nconst REFRESH_INTERVAL = 30 * 60 * 1000; \/\/ 30 minutes\nconst RAIN_DENSITY = 15;\nconst SNOW_DENSITY = 20;\nconst START_DELAY_SEC = 7;\nconst FADE_IN_MS = 500;\nconst MAX_DPR = 1.5;\nconst FALLBACK_COORDS = { lon: 4.909, lat: 52.7557 };\nconst NO_EFFECT_TIMEOUT_MS = 10000; \/\/ Hide button if no effect after 10s\n\nlet weatherEffectActive = null;\nlet hasStartedOnce = false;\nlet pendingStartTimeout = null;\nlet autoHideTimeout = null;\nlet weatherScriptRunning = true;\nlet weatherInterval = null;\n\n\/\/ === CREATE WEATHER BUTTON ===\nconst weatherBtn = document.createElement(\"div\");\nweatherBtn.id = \"weather-btn\";\nweatherBtn.className = \"weather-btn\";\nweatherBtn.innerHTML = `<i class=\"fa-solid fa-umbrella\"><\/i>`;\nweatherBtn.style.cursor = \"pointer\";\nweatherBtn.style.position = \"absolute\";\nweatherBtn.style.right = \"10px\";\nweatherBtn.style.top = \"50px\";\nweatherBtn.style.display = \"block\"; \/\/ visible initially\ndocument.body.appendChild(weatherBtn);\n\n\/\/ Button hover styling\nconst styleEl = document.createElement(\"style\");\nstyleEl.innerHTML = `\n#weather-btn i {\n  font-size: 24px;\n  color: rgba(0,0,0,0.8);\n  transition: color 0.3s ease;\n}\n#weather-btn:hover i {\n  color: #fdcc17;\n}\n`;\ndocument.head.appendChild(styleEl);\n\n\/\/ Button click toggle\nweatherBtn.addEventListener(\"click\", () => {\n  if (weatherScriptRunning) {\n    \/\/ Stop weather\n    weatherScriptRunning = false;\n    clearInterval(weatherInterval);\n    clearPendingStart();\n    stopEffect();\n    canvas.style.opacity = \"0\";\n    weatherEffectActive = null;\n    console.log(\"Weather effects stopped.\");\n    weatherBtn.style.display = \"block\"; \/\/ remain visible for restart\n  } else {\n    \/\/ Restart weather\n    weatherScriptRunning = true;\n    console.log(\"Weather effects started.\");\n    checkWeather();\n    weatherInterval = setInterval(checkWeather, REFRESH_INTERVAL);\n    scheduleAutoHide();\n  }\n});\n\n\/\/ === SETUP CANVAS ===\nconst canvas = document.createElement(\"canvas\");\ncanvas.id = \"weatherCanvas\";\ncanvas.style.position = \"fixed\";\ncanvas.style.top = \"0\";\ncanvas.style.left = \"0\";\ncanvas.style.width = \"100%\";\ncanvas.style.height = \"100%\";\ncanvas.style.pointerEvents = \"none\";\ncanvas.style.zIndex = \"9999\";\ncanvas.style.opacity = \"0\";\ncanvas.style.transition = \"opacity 0ms linear\";\ndocument.body.appendChild(canvas);\n\nfunction getScale() {\n  const dpr = window.devicePixelRatio || 1;\n  return Math.min(dpr, MAX_DPR);\n}\n\nconst ctx = canvas.getContext(\"2d\", { alpha: true });\n\nfunction resizeCanvas() {\n  const scale = getScale();\n  canvas.width = Math.floor(window.innerWidth * scale);\n  canvas.height = Math.floor(window.innerHeight * scale);\n  ctx.setTransform(scale, 0, 0, scale, 0, 0);\n  if (weatherEffectActive === \"snow\") {\n    const step = 10;\n    const targetLen = Math.ceil(canvas.width \/ getScale() \/ step);\n    if (snowSurface.length !== targetLen) {\n      const old = snowSurface.slice();\n      snowSurface = Array.from({ length: targetLen }, (_, i) => {\n        const mapped = Math.floor((i \/ targetLen) * old.length);\n        return old[mapped] || 0;\n      });\n    }\n  }\n}\nresizeCanvas();\nwindow.addEventListener(\"resize\", resizeCanvas);\n\n\/\/ === PARTICLE STORAGE ===\nlet particles = [];\nlet snowSurface = [];\nlet animationId;\n\n\/\/ === SMALL UTILS ===\nfunction fadeInCanvas(durationMs) {\n  canvas.style.transition = `opacity ${durationMs}ms ease`;\n  canvas.style.opacity = \"0\";\n  requestAnimationFrame(() => {\n    canvas.style.opacity = \"1\";\n  });\n}\n\nfunction clearPendingStart() {\n  if (pendingStartTimeout != null) {\n    clearTimeout(pendingStartTimeout);\n    pendingStartTimeout = null;\n  }\n}\n\nfunction scheduleAutoHide() {\n  clearTimeout(autoHideTimeout);\n  autoHideTimeout = setTimeout(() => {\n    if (!weatherEffectActive && weatherScriptRunning) {\n      weatherBtn && (weatherBtn.style.display = \"none\");\n    }\n  }, NO_EFFECT_TIMEOUT_MS);\n}\n\n\/\/ === RAIN EFFECT ===\nfunction startRain() {\n  cancelAnimationFrame(animationId);\n  const count = Math.floor(250 * (RAIN_DENSITY \/ 100));\n  particles = Array.from({ length: count }, () => {\n    const alpha = 0.3 + Math.random() * 0.5;\n    return {\n      x: Math.random() * canvas.width \/ getScale(),\n      y: Math.random() * canvas.height \/ getScale(),\n      len: 20 + Math.random() * 20,\n      speed: 10 + Math.random() * 10,\n      wind: 0.4,\n      color: `rgba(174,194,224,${alpha.toFixed(3)})`,\n    };\n  });\n  ctx.lineWidth = 1.2;\n  ctx.lineCap = \"round\";\n  function drawRain() {\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n    for (const p of particles) {\n      ctx.strokeStyle = p.color;\n      ctx.beginPath();\n      ctx.moveTo(p.x, p.y);\n      ctx.lineTo(p.x + p.wind, p.y + p.len);\n      ctx.stroke();\n      p.y += p.speed;\n      p.x += p.wind;\n      const viewW = canvas.width \/ getScale();\n      const viewH = canvas.height \/ getScale();\n      if (p.y > viewH) {\n        p.y = -20;\n        p.x = Math.random() * viewW;\n      }\n      if (p.x > viewW) p.x = 0;\n    }\n    animationId = requestAnimationFrame(drawRain);\n  }\n  drawRain();\n}\n\n\/\/ === SNOW EFFECT ===\nfunction startSnow() {\n  cancelAnimationFrame(animationId);\n  const count = Math.floor(200 * (SNOW_DENSITY \/ 100));\n  particles = Array.from({ length: count }, () => ({\n    x: Math.random() * canvas.width \/ getScale(),\n    y: Math.random() * canvas.height \/ getScale(),\n    r: 1 + Math.random() * 3,\n    speed: 0.5 + Math.random() * 1,\n  }));\n  const step = 10;\n  const viewW = canvas.width \/ getScale();\n  snowSurface = Array.from({ length: Math.ceil(viewW \/ step) }, () => 0);\n  function getSnowHeight(x) {\n    const stepPx = viewW \/ snowSurface.length;\n    const idx = Math.floor(x \/ stepPx);\n    return snowSurface[idx] || 0;\n  }\n  function drawSnow() {\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n    ctx.beginPath();\n    ctx.moveTo(0, canvas.height \/ getScale());\n    for (let i = 0; i < snowSurface.length; i++) {\n      const x = i * step;\n      const y = (canvas.height \/ getScale()) - snowSurface[i];\n      ctx.lineTo(x, y);\n    }\n    ctx.lineTo(canvas.width \/ getScale(), canvas.height \/ getScale());\n    ctx.closePath();\n    ctx.fillStyle = \"rgba(255,255,255,0.9)\";\n    ctx.fill();\n    ctx.fillStyle = \"rgba(255,255,255,0.9)\";\n    const viewH = canvas.height \/ getScale();\n    for (const p of particles) {\n      ctx.beginPath();\n      ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);\n      ctx.fill();\n      p.y += p.speed;\n      p.x += Math.sin(p.y \/ 50) * 0.5;\n      if (p.y > viewH - (getSnowHeight(p.x) + 1)) {\n        p.y = Math.random() * -10;\n        p.x = Math.random() * viewW;\n        const idx = Math.floor((p.x \/ viewW) * snowSurface.length);\n        if (snowSurface[idx] < 20) {\n          snowSurface[idx] += 0.05 + Math.random() * 0.1;\n        }\n      }\n    }\n    animationId = requestAnimationFrame(drawSnow);\n  }\n  drawSnow();\n}\n\n\/\/ === STOP EFFECT ===\nfunction stopEffect() {\n  cancelAnimationFrame(animationId);\n  ctx.clearRect(0, 0, canvas.width, canvas.height);\n  particles = [];\n  snowSurface = [];\n  weatherEffectActive = null;\n}\n\n\/\/ === MAIN WEATHER HANDLER ===\nfunction handleWeather(condition) {\n  if (!weatherScriptRunning) return;\n  if (weatherEffectActive === condition) return;\n  clearPendingStart();\n  stopEffect();\n  const startEffect = () => {\n    if (condition === \"rain\") {\n      fadeInCanvas(FADE_IN_MS);\n      startRain();\n      weatherBtn && (weatherBtn.style.display = \"block\");\n    } else if (condition === \"snow\") {\n      fadeInCanvas(FADE_IN_MS);\n      startSnow();\n      weatherBtn && (weatherBtn.style.display = \"block\");\n    } else {\n      canvas.style.opacity = \"0\";\n      scheduleAutoHide();\n    }\n    weatherEffectActive = condition;\n  };\n  if (!hasStartedOnce && (condition === \"rain\" || condition === \"snow\") && START_DELAY_SEC > 0) {\n    pendingStartTimeout = setTimeout(() => {\n      startEffect();\n      hasStartedOnce = true;\n    }, START_DELAY_SEC * 1000);\n  } else {\n    startEffect();\n    hasStartedOnce = true;\n  }\n}\n\n\/\/ === GEO + WEATHER ===\nfunction getCurrentPositionWithFallback() {\n  return new Promise((resolve) => {\n    if (!navigator.geolocation) {\n      resolve({ lat: FALLBACK_COORDS.lat, lon: FALLBACK_COORDS.lon });\n      return;\n    }\n    navigator.geolocation.getCurrentPosition(\n      (pos) => resolve({ lat: pos.coords.latitude, lon: pos.coords.longitude }),\n      () => resolve({ lat: FALLBACK_COORDS.lat, lon: FALLBACK_COORDS.lon })\n    );\n  });\n}\n\n\/\/ === TEST OR LIVE ===\nasync function checkWeather() {\n  if (!weatherScriptRunning) return;\n  if (weatherTest === \"rain\" || weatherTest === \"snow\" || weatherTest === \"off\") {\n    handleWeather(weatherTest === \"off\" ? null : weatherTest);\n    return;\n  }\n  try {\n    const { lat, lon } = await getCurrentPositionWithFallback();\n    if (!WEATHER_API_KEY) {\n      console.warn(\"No WEATHER_API_KEY supplied; skipping live weather.\");\n      handleWeather(null);\n      return;\n    }\n    const url = `https:\/\/api.openweathermap.org\/data\/2.5\/weather?lat=${lat}&lon=${lon}&appid=${WEATHER_API_KEY}`;\n    const res = await fetch(url);\n    const data = await res.json();\n    const main = (data && data.weather && data.weather[0] && data.weather[0].main || \"\").toLowerCase();\n    if (main.includes(\"rain\")) handleWeather(\"rain\");\n    else if (main.includes(\"snow\")) handleWeather(\"snow\");\n    else handleWeather(null);\n  } catch (err) {\n    console.warn(\"Weather check failed:\", err);\n    handleWeather(null);\n  }\n}\n\n\/\/ === INIT ===\ncheckWeather();\nscheduleAutoHide();\nweatherInterval = setInterval(checkWeather, REFRESH_INTERVAL);\n<\/script>\n\n\n\n<!-- Refresh Icon -->\n<div id=\"refresh-btn\">\n  <i class=\"fas fa-sync-alt\"><\/i>\n<\/div>\n\n<style>\n#refresh-btn {\n  cursor: pointer;\n  position: absolute;\n  right: 10px;\n  top: 10px;\n}\n\n#refresh-btn i {\n  font-size: 24px;            \/* adjust size *\/\n  color: rgba(0, 0, 0, 0.8);  \/* black with 80% opacity *\/\n  transition: color 0.3s ease;\n}\n\n#refresh-btn:hover i {\n  color: #fdcc17;             \/* solid #AF7933 on hover *\/\n}\n<\/style>\n\n<script>\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function() {\n  location.reload(); \/\/ same as F5\n});\n<\/script>\n\n\n\n<!-- BASIC CSS -->\n<style>\n\tbody {\n\t\tbackground-color: black;\n\t\tfont-family: 'Friz-Quadrata','helvetica',Arial !important;\n\t\tbackground-image: url(\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/wow-logobackground-right.png\");\n\t\tbackground-repeat: no-repeat;\n\t\tbackground-position: right !important;\n\t}\n<\/style>\n\n<!-- ICON STYLING -->\n<!-- ICON SOURCE: https:\/\/www.wowhead.com\/icons\/ -->\n<style>\n.iconwow {\n  height: 68px;\n  width: 68px;\n  position: absolute;\n}\n\n.iconwow ins {\n  height: 56px;\n  width: 56px;\n  left: 6px;\n  top: 6px;\n  border-radius: 4px;\n  background-repeat: no-repeat;\n  background-size: contain;\n  display: block;\n  position: absolute;\n  z-index: 5;\n}\n\n.iconwow del {\n  height: 68px;\n  width: 68px;\n  background-image: url(https:\/\/cdr.ubn.one\/wow\/frame.png);\n  background-repeat: no-repeat;\n  display: block;\n  left: 0;\n  position: absolute;\n  top: 0;\n  z-index: 10;\n}\n<\/style>\n\n<!-- TITLE TEXT -->\n<style>\n.title_text {\nbackground: linear-gradient(180deg, rgb(255, 241, 111), #FFC600, rgb(255, 188, 2));\nbackground-clip: border-box;\nbackground-clip: text;\n-webkit-background-clip: text;\n-webkit-text-fill-color: transparent;\npadding-bottom: 2px;\ntext-shadow: 0 4px 4px rgba(0, 0, 0, 0.15);\nfont-size: 19px;\nfont-weight: 400;\nline-height: 1.8;\n}\n<\/style>\n\n\n\n<!-- IMAGE SEPERATOR -->\n<style>\n.wow-img-sep {\n   top: 0px;\n   left: 675px;\n   position: absolute;\n   height: 1080px;\n   width: 183px;\n   background-image: url(\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/11\/wow-sep.png\");\n   background-repeat: no-repeat; \n}\n<\/style>\n<div class=\"wow-img-sep\"><\/div>\n\n\n\n<!-- LOGO DIV STYLE -->\n<style>\n.logo-div-logo {\n  display: block;\n  position: absolute;\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: contain;\n  width: 480px;\n  height: 380px;\n  margin-left: -624px;\n  margin-top: 107px;\n}\n<\/style>\n\n<div class=\"logo-div-logo\"><\/div>\n\n<script>\n\/\/ === CONFIGURATION ===\nconst SHOW_DURATION_DAYS = 7;        \/\/ Number of days each expansion logo stays active\nconst TEST_OVERRIDE = null;          \/\/ Set expansion number (1\u201311) for testing, null = default\n\n\/\/ === EXPANSION DATA ===\nconst expansions = [\n  { id: 1, name: \"The Burning Crusade\",    date: \"16-01\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-tbc-logo.webp\",  marginCorrection: 0 },\n  { id: 2, name: \"Wrath of the Lich King\", date: \"13-11\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-wotlk-logo.webp\", marginCorrection: 0 },\n  { id: 3, name: \"Cataclysm\",              date: \"07-12\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-cata-logo.webp\",  marginCorrection: 0 },\n  { id: 4, name: \"Mists of Pandaria\",      date: \"25-09\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-mop-logo.webp\",   marginCorrection: 0 },\n  { id: 5, name: \"Warlords of Draenor\",    date: \"13-11\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-wod-logo.webp\",   marginCorrection: 0 },\n  { id: 6, name: \"Legion\",                 date: \"30-08\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-legion-logo.webp\", marginCorrection: 0 },\n  { id: 7, name: \"Battle for Azeroth\",     date: \"14-08\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-bfa-logo.webp\",   marginCorrection: 0 },\n  { id: 8, name: \"Shadowlands\",            date: \"23-11\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-shadowlands-logo.webp\", marginCorrection: 0 },\n  { id: 9, name: \"Dragonflight\",           date: \"29-11\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-dragonflight-logo.webp\", marginCorrection: 0 },\n  { id: 10, name: \"The War Within\",        date: \"26-08\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-warwithin-logo.webp\", marginCorrection: 0 },\n  { id: 11, name: \"Midnight\",   \t\t   date: \"03-03\", logo: \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-midnight-logo.webp\", marginCorrection: 0 }\n];\n\n\/\/ === DEFAULT LOGO ===\nconst DEFAULT_LOGO = \"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-logo-main.png\";\nconst DEFAULT_MARGIN = 107;\n\n\/\/ === MAIN SCRIPT ===\n(function() {\n  const logoDiv = document.querySelector(\".logo-div-logo\");\n  const now = new Date();\n\n  \/\/ Helper: check if current date is within X days of a given expansion release\n  function isWithinDays(dayMonth, rangeDays) {\n    const [day, month] = dayMonth.split(\"-\").map(Number);\n    const year = now.getFullYear();\n    const release = new Date(year, month - 1, day);\n    const end = new Date(release);\n    end.setDate(end.getDate() + rangeDays);\n\n    return now >= release && now <= end;\n  }\n\n  let activeExpansions = [];\n\n  \/\/ Test override active?\n  if (TEST_OVERRIDE) {\n    const exp = expansions.find(e => e.id === TEST_OVERRIDE);\n    if (exp) activeExpansions = [exp];\n  } else {\n    \/\/ Otherwise find expansions currently within date window\n    activeExpansions = expansions.filter(exp => isWithinDays(exp.date, SHOW_DURATION_DAYS));\n  }\n\n  \/\/ Pick logo\n  let chosenLogo = DEFAULT_LOGO;\n  let marginCorrection = 0;\n\n  if (activeExpansions.length > 0) {\n    const chosen = activeExpansions[Math.floor(Math.random() * activeExpansions.length)];\n    chosenLogo = chosen.logo;\n    marginCorrection = chosen.marginCorrection || 0;\n    console.log(`Active WoW expansion: ${chosen.name} (${chosen.date})`);\n  } else {\n    console.log(\"No active expansion. Showing default WoW logo.\");\n  }\n\n  \/\/ Apply logo and margin correction\n  logoDiv.style.backgroundImage = `url(\"${chosenLogo}\")`;\n  logoDiv.style.marginTop = `${DEFAULT_MARGIN + marginCorrection}px`;\n})();\n<\/script>\n\n\n\n<!-- YOUTUBE ICON + BAR -->\n<style>\n.title_yt_bar {\n  position: absolute;\n  left: 129px;\n  top: 597px;\n  width: 502px;\n  height: 34px;\n  background-image: url(\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/titlebar.png\");\n  background-position: right;\n  text-align: center;\n}\n<\/style>\n\n<div class=\"title_yt_bar\"><span class=\"title_text\">Livestream<\/span><\/div>\n\n<div class=\"iconwow\" style=\"top: 578px; left: 98px;\">\n<ins style=\"background-image: url('https:\/\/cdr.ubn.one\/wow\/camera.jpg');\"><\/ins>\n<del><\/del>\n<\/div>\n\n\n\n<!-- CONTAINING YT DIV -->\n<style>\n.wowyt_container {\n    background-color: none;\n    position: absolute;\ntop: 630px;\n  left: 129px;\n  width: 502px;\n  height: 330px;\n\n  box-shadow: 0px 15px 35px 0px rgba(0, 0, 0, 0.12);\n  border: 1px solid #64605d;\n  background-image: url(\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-frame-background.png\");\n}\n<\/style>\n<div class=\"wowyt_container\"><\/div>\n\n\n\n<!-- YOUTUBE RSS -->\n<style>\n  .yt_container { position: absolute; top: 631px; left: 130px; }\n  #yt-live { position: relative; border: none; display: block; margin: 0 auto; }\n<\/style>\n\n<div class=\"yt_container\">\n   <div id=\"yt-live\"><\/div>\n<\/div>\n\n<script>\n    \/\/ === CONFIGURATION ===\n    const DISPLAY_WIDTH = \"500px\"; \/\/ any valid CSS unit (px, %, vw, etc.)\n    const MAX_WIDTH = \"1280px\";   \/\/ maximum width\n    const MAX_HEIGHT = \"720px\";   \/\/ maximum height\n    \/\/ ======================\n\n    const RSS_URL = \"https:\/\/www.youtube.com\/feeds\/videos.xml?channel_id=UCAmE2TXadPvh2Jtk9RWOgfw\";\n    const PROXY_URL = `https:\/\/api.allorigins.win\/get?url=${encodeURIComponent(RSS_URL)}`;\n\n    let livestreamVideoId = null;\n    let player;\n\n    function applyIframeSize() {\n        const container = document.getElementById(\"yt-live\");\n        container.style.width = DISPLAY_WIDTH;\n        container.style.height = `calc(${DISPLAY_WIDTH} * 9 \/ 16)`;\n        container.style.maxWidth = MAX_WIDTH;\n        container.style.maxHeight = MAX_HEIGHT;\n    }\n\n    async function loadLatestLivestream() {\n        try {\n            const res = await fetch(PROXY_URL);\n            const data = await res.json();\n            const text = data.contents;\n\n            const parser = new DOMParser();\n            const xml = parser.parseFromString(text, \"application\/xml\");\n            const entries = xml.getElementsByTagName(\"entry\");\n\n            const YT_NS = \"http:\/\/www.youtube.com\/xml\/schemas\/2015\";\n\n            for (let entry of entries) {\n                const titleNode = entry.getElementsByTagName(\"title\")[0];\n                if (!titleNode) continue;\n\n                const title = titleNode.textContent || \"\";\n                if (title.toLowerCase().includes(\"livestream\")) {\n                    const videoIdNode = entry.getElementsByTagNameNS(YT_NS, \"videoId\")[0];\n                    if (videoIdNode) {\n                        livestreamVideoId = videoIdNode.textContent;\n                        break;\n                    }\n                }\n            }\n\n            if (livestreamVideoId) {\n                \/\/ Load YouTube API if not already loaded\n                if (!window.YT) {\n                    const tag = document.createElement('script');\n                    tag.src = \"https:\/\/www.youtube.com\/iframe_api\";\n                    document.body.appendChild(tag);\n                } else {\n                    initPlayer();\n                }\n            } else {\n                console.warn(\"No livestream found in the feed.\");\n            }\n\n        } catch (err) {\n            console.error(\"Error loading RSS feed:\", err);\n        }\n    }\n\n    function initPlayer() {\n        if (!livestreamVideoId) return;\n        player = new YT.Player('yt-live', {\n            videoId: livestreamVideoId,\n            playerVars: {\n                autoplay: 1,\n                mute: 1, \/\/ change to 0 if you want unmuted by default\n                modestbranding: 1,\n                rel: 0\n            },\n            events: {\n                'onReady': onPlayerReady\n            }\n        });\n        applyIframeSize();\n    }\n\n    function onYouTubeIframeAPIReady() {\n        initPlayer();\n    }\n\n    function onPlayerReady(event) {\n        event.target.setPlaybackQuality('hd720');\n        event.target.playVideo();\n    }\n\n    \/\/ Initial load only\n    loadLatestLivestream();\n\n    \/\/ Apply size immediately (before video loads)\n    applyIframeSize();\n<\/script>\n\n\n\n<!-- YOUTUBE BUTTON -->\n<style>\n.yt_button {\nbackground-image: url(\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-button-subscribe.png\");\ntop: 924px;\nleft: 478px;\nwidth: 144px;\nheight: 23px;\nposition: absolute;\ntext-align: center;\n}\n<\/style>\n\n<a href=\"https:\/\/www.youtube.com\/@Meisio?sub_confirmation=1\" target=\"_blank\"><div class=\"yt_button\"><\/div><\/a>\n\n\n\n<!-- BLIZZARD DIV -->\n<style>\n.wowblizz_container {\n    background-color: none;\n    position: absolute;\n    top: 999px;\n    left: 129px;\n    width: 502px;\n    height: 41px;\n\n  box-shadow: 0px 15px 35px 0px rgba(0, 0, 0, 0.12);\n  border: 1px solid #64605d;\n  background-image: url(\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-frame-background.png\");\n}\n.wowblizz_txt {\n    position: absolute;\n    top: 1006px;\n    left: 175px;\n    width: 440px;\n    height: 28px;\n    font-size: 15px;\n    color: #fdcc17;\n}\n<\/style>\n\n<div class=\"wowblizz_container\"><\/div>\n<div class=\"wowblizz_txt\">WoW IP Assets used with respect under Copyright &amp; Fair Use.<\/div>\n\n<a href=\"https:\/\/worldofwarcraft.com\/\" target=\"_blank\"><div class=\"iconwow\" style=\"top: 984px; left: 98px;\">\n<ins style=\"background-image: url('https:\/\/cdr.ubn.one\/wow\/blizzard.jpg');\"><\/ins>\n<del><\/del>\n<\/div><\/a>\n\n\n\n<!-- WOW ZONE MAP -->\n<iframe src=\"https:\/\/cdr.ubn.one\/wow\/zones.html\" style=\"position:absolute;top:40px;left:896px; width:880px;height:255px;background:transparent;border:0;\" frameborder=\"0\" scrolling=\"no\" allowtransparency=\"true\"><\/iframe>\n\n<div style=\"top: 222px; left: 1704px; position: absolute; width: 43px; height:43px;\"><a href=\"https:\/\/www.wowhead.com\/zones\" target=\"_blank\"><img decoding=\"async\" src=\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-mapborder-headicon.png\" \/><\/a><\/div>\n\n\n\n<!-- EVENTS -->\n<iframe src=\"https:\/\/cdr.ubn.one\/wow\/events.html\" style=\"position:absolute;top:318px;left:896px; width:865px;height:230px;background:transparent;border:0;\" frameborder=\"0\" scrolling=\"no\" allowtransparency=\"true\"><\/iframe>\n\n\n\n<!-- CONTAINING RSS DIV -->\n<style>\n.wowrss_container {\n    background-color: none;\n    position: absolute;\ntop: 630px;\n  left: 931px;\n  width: 830px;\n  height: 410px;\n  box-shadow: 0px 15px 35px 0px rgba(0, 0, 0, 0.12);\n  border: 1px solid #64605d;\n  background-image: url(\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/10\/wow-frame-background.png\");\n}\n<\/style>\n<div class=\"wowrss_container\"><\/div>\n\n\n\n<style>\n.title_rss_bar {\n  position: absolute;\n  left: 931px;\n  top: 597px;\n  width: 830px;\n  height: 34px;\n  background-image: url(\"https:\/\/labs.ubn.one\/signage\/wp-content\/uploads\/2025\/09\/titlebar.png\");\n  background-position: right;\n  text-align: center;\n}\n<\/style>\n\n<div class=\"title_rss_bar\"><span class=\"title_text\">Wowhead News<\/span><\/div>\n\n\n\n<!-- RSS FEED STYLE -->\n<style>\n\/* --- Hide unwanted elements --- *\/\n.wp_rss_retriever_container {\n    display: none !important;\n}\n.wp_rss_retriever_date {\n    display: none !important;\n}\n.wp_rss_retriever_metadata {\n    display: none !important;\n}\n\n\/* --- Parent container --- *\/\n.wp_rss_retriever {\n    position: absolute !important;\n    left: 1301px !important;\n    top: 650px; \/* adjust vertical position *\/\n    transform: translateX(-50%) !important;\n    width: 660px !important; \/* 3 \u00d7 200px + 2 \u00d7 30px gap *\/\n    height: 220px !important; \/* enough for image + title *\/\n}\n\n\/* --- Each feed item (li) --- *\/\n.wp_rss_retriever li {\n    position: absolute !important;\n    display: flex !important;\n    flex-direction: column !important; \/* image above title *\/\n    align-items: center !important;\n    text-align: center !important;\n    width: 200px !important;\n}\n\n\/* --- Position each item manually --- *\/\n.wp_rss_retriever li:nth-child(1) {\n    left: 0 !important;\n}\n.wp_rss_retriever li:nth-child(2) {\n    left: 290px !important; \/* image width + gap *\/\n}\n.wp_rss_retriever li:nth-child(3) {\n    left: 570px !important;\n}\n\n\/* --- Image styling --- *\/\n.wp_rss_retriever_image {\n    order: 1 !important;          \/* keep image first *\/\n    width: 220px !important;      \/* set width *\/\n    aspect-ratio: 16 \/ 10 !important; \/* enforce 16:10 *\/\n    height: auto !important;      \/* override inline height *\/\n    display: block !important;\n    overflow: hidden !important;\n}\n\n.wp_rss_retriever_image img {\n    width: 100% !important;\n    height: 100% !important;\n    object-fit: cover !important;\n    display: block !important;\n    border-radius: 0px;\n}\n\n\/* --- Title styling --- *\/\n.wp_rss_retriever_title {\n    order: 2 !important;   \/* move title below the image *\/\n    width: 220px !important;   \/* match image width *\/\n    margin-top: 10px !important;\n    color: #fdcc17 !important;\n    text-decoration: none !important;\n    font-family: 'Friz-Quadrata','helvetica',Arial;\n    font-size: 20px;\n    position: absolute;\n    top: 150px;\n}\n\n.wp_rss_retriever_title:hover {\n    text-decoration: underline !important;\n}\n.element {\n   width: 400px !important;\n}\n<\/style>\n\n\n\n<!-- NEWS ICON -->\n<div class=\"iconwow\" style=\"top: 578px; left: 896px;\">\n<ins style=\"background-image: url('https:\/\/cdr.ubn.one\/wow\/paperbundle.jpg');\"><\/ins>\n<del><\/del>\n<\/div>\n\n\n<div class=\"wprss_ajax\" data-id=\"rss118103f3e0\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/labs.ubn.one\/signage\/wp-content\/plugins\/wp-rss-retriever\/inc\/imgs\/ajax-loader.gif\" alt=\"Loading RSS Feed\" width=\"16\" height=\"16\"><\/div>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Livestream WoW IP Assets used with respect under Copyright &amp; Fair Use. Wowhead News<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-31924","page","type-page","status-publish","hentry"],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/labs.ubn.one\/signage\/index.php\/wp-json\/wp\/v2\/pages\/31924","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/labs.ubn.one\/signage\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/labs.ubn.one\/signage\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/labs.ubn.one\/signage\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/labs.ubn.one\/signage\/index.php\/wp-json\/wp\/v2\/comments?post=31924"}],"version-history":[{"count":246,"href":"https:\/\/labs.ubn.one\/signage\/index.php\/wp-json\/wp\/v2\/pages\/31924\/revisions"}],"predecessor-version":[{"id":36029,"href":"https:\/\/labs.ubn.one\/signage\/index.php\/wp-json\/wp\/v2\/pages\/31924\/revisions\/36029"}],"wp:attachment":[{"href":"https:\/\/labs.ubn.one\/signage\/index.php\/wp-json\/wp\/v2\/media?parent=31924"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}