Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* WEATHER HUD — WITH MODAL OPTIONS WINDOW (Option C)
- Fully integrated script, ready to paste.
- Includes: options button, modal, theme, units, animations, update interval, opacity, scale, location, 5-day forecast.
- */
- (function() {
- /* --------------------------
- CONFIG + DEFAULT SETTINGS
- ---------------------------*/
- let city = "Greenfield,OH";
- const DEFAULTS = {
- city,
- updateIntervalMin: 10,
- animationsEnabled: true,
- showForecast: true,
- opacity: 1,
- scale: 1,
- theme: "auto",
- units: "F",
- left: null,
- top: null,
- width: null
- };
- const STORAGE_KEY = "weatherHudSettings_v1";
- let settings = Object.assign({}, DEFAULTS);
- try {
- const saved = JSON.parse(localStorage.getItem(STORAGE_KEY));
- if (saved && typeof saved === "object") settings = Object.assign(settings, saved);
- } catch(e){}
- city = settings.city || city;
- const icons = {
- "sunny": "☀️",
- "clear": "☀️",
- "cloud": "☁️",
- "overcast": "☁️",
- "rain": "🌧️",
- "drizzle": "🌦️",
- "snow": "❄️",
- "thunder": "⛈️",
- "fog": "🌫️",
- "mist": "🌫️"
- };
- function saveSettings(){
- try {
- localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
- } catch(e){}
- }
- function tempColor(temp, units){
- if (isNaN(temp)) return "#fff";
- let tF = units === "F" ? temp : (temp*9/5)+32;
- if (tF <= 32) return "#00f";
- if (tF <= 60) return "#0ff";
- if (tF <= 80) return "#ff0";
- return "#f00";
- }
- function weatherGradient(desc, temp, units){
- desc = (desc||"").toLowerCase();
- let base;
- if (desc.includes("sunny") || desc.includes("clear")) base = "linear-gradient(135deg,#FFD700,#FFA500)";
- else if (desc.includes("cloud") || desc.includes("overcast")) base = "linear-gradient(135deg,#B0C4DE,#778899)";
- else if (desc.includes("rain") || desc.includes("drizzle")) base = "linear-gradient(135deg,#1E90FF,#00008B)";
- else if (desc.includes("thunder")) base = "linear-gradient(135deg,#4B0082,#2F4F4F)";
- else if (desc.includes("snow")) base = "linear-gradient(135deg,#E0FFFF,#87CEFA)";
- else if (desc.includes("fog") || desc.includes("mist")) base = "linear-gradient(135deg,#D3D3D3,#A9A9A9)";
- else base = "linear-gradient(135deg,#FFD700,#FF8C00)";
- const tF = units==="F" ? temp : (temp*9/5)+32;
- if (tF !== null && tF <= 32) base += ",rgba(0,0,255,0.12)";
- else if (tF !== null && tF >= 90) base += ",rgba(255,0,0,0.12)";
- return base;
- }
- function fmtTempFromF(F, units){
- const v = parseFloat(F);
- if (isNaN(v)) return {num:NaN,text:F};
- if (units==="F") return {num:Math.round(v), text:`${Math.round(v)}°F`};
- const c = Math.round((v-32)*5/9);
- return {num:c,text:`${c}°C`};
- }
- /* --------------------------
- HUD BASE
- ---------------------------*/
- const hud = document.createElement("div");
- hud.style.position = "fixed";
- hud.style.top = "10px";
- hud.style.right = "10px";
- hud.style.background = "rgba(0,0,0,0.6)";
- hud.style.color = "#fff";
- hud.style.padding = "15px 20px";
- hud.style.borderRadius = "12px";
- hud.style.fontFamily = "Arial, sans-serif";
- hud.style.zIndex = "9999";
- hud.style.width = "min(90vw,320px)";
- hud.style.textAlign = "center";
- hud.style.boxShadow = "0 0 20px rgba(0,0,0,0.7)";
- hud.style.cursor = "move";
- hud.style.userSelect = "none";
- hud.style.opacity = settings.opacity;
- hud.style.transform = `scale(${settings.scale})`;
- hud.style.transition = "opacity .3s, transform .15s, width .25s, background .4s";
- const canvas = document.createElement("canvas");
- canvas.style.position = "absolute";
- canvas.style.inset = "0";
- canvas.style.width = "100%";
- canvas.style.height = "100%";
- canvas.style.pointerEvents = "none";
- hud.appendChild(canvas);
- const ctx = canvas.getContext("2d");
- const closeBtn = document.createElement("span");
- closeBtn.textContent = "✖";
- closeBtn.style.position = "absolute";
- closeBtn.style.top = "5px";
- closeBtn.style.right = "10px";
- closeBtn.style.cursor = "pointer";
- closeBtn.style.fontWeight = "bold";
- closeBtn.title = "Close HUD";
- hud.appendChild(closeBtn);
- const optionsBtn = document.createElement("span");
- optionsBtn.textContent = "⚙";
- optionsBtn.style.position = "absolute";
- optionsBtn.style.top = "5px";
- optionsBtn.style.left = "10px";
- optionsBtn.style.cursor = "pointer";
- optionsBtn.style.fontWeight = "bold";
- optionsBtn.title = "Options";
- hud.appendChild(optionsBtn);
- const titleBar = document.createElement("div");
- titleBar.style.background = "rgba(0,0,0,0.85)";
- titleBar.style.padding = "5px 0";
- titleBar.style.borderRadius = "8px 8px 0 0";
- titleBar.style.marginBottom = "8px";
- titleBar.style.cursor = "move";
- titleBar.style.display = "flex";
- titleBar.style.justifyContent = "center";
- const title = document.createElement("h3");
- title.textContent = city + " Weather";
- title.style.margin = "0";
- title.style.fontSize = "16px";
- title.style.textDecoration = "underline";
- title.title = "Click to change location";
- titleBar.appendChild(title);
- hud.appendChild(titleBar);
- const forecast = document.createElement("p");
- forecast.style.margin = "0";
- forecast.style.fontSize = "14px";
- const timeEl = document.createElement("p");
- timeEl.style.margin = "5px 0 10px";
- const fiveDay = document.createElement("div");
- fiveDay.style.display = settings.showForecast ? "flex" : "none";
- fiveDay.style.justifyContent = "space-between";
- fiveDay.style.flexWrap = "wrap";
- fiveDay.style.marginTop = "10px";
- fiveDay.style.fontSize = "12px";
- hud.appendChild(forecast);
- hud.appendChild(timeEl);
- hud.appendChild(fiveDay);
- document.body.appendChild(hud);
- /* --------------------------
- RESIZE HANDLE
- ---------------------------*/
- const resizeHandle = document.createElement("div");
- resizeHandle.style.position = "absolute";
- resizeHandle.style.right = "6px";
- resizeHandle.style.bottom = "6px";
- resizeHandle.style.width = "16px";
- resizeHandle.style.height = "16px";
- resizeHandle.style.cursor = "nwse-resize";
- resizeHandle.style.opacity = "0.6";
- hud.appendChild(resizeHandle);
- /* --------------------------
- MODAL OPTIONS (OPTION C)
- ---------------------------*/
- const modalBackdrop = document.createElement("div");
- modalBackdrop.style.position = "fixed";
- modalBackdrop.style.inset = "0";
- modalBackdrop.style.background = "rgba(0,0,0,0.35)";
- modalBackdrop.style.backdropFilter = "blur(4px)";
- modalBackdrop.style.zIndex = "10010";
- modalBackdrop.style.display = "none";
- modalBackdrop.style.justifyContent = "center";
- modalBackdrop.style.alignItems = "center";
- modalBackdrop.style.transition = "opacity 0.18s";
- const modalBox = document.createElement("div");
- modalBox.style.width = "min(90vw,560px)";
- modalBox.style.maxHeight = "80vh";
- modalBox.style.overflow = "auto";
- modalBox.style.background = "rgba(0,0,0,0.95)";
- modalBox.style.borderRadius = "12px";
- modalBox.style.padding = "18px";
- modalBox.style.boxShadow = "0 20px 60px rgba(0,0,0,0.6)";
- modalBox.style.transform = "scale(0.96)";
- modalBox.style.transition = "transform .2s, opacity .18s";
- modalBox.style.color = "#fff";
- modalBox.innerHTML = `
- <div style="display:flex;justify-content:space-between;align-items:center;">
- <h2 style="margin:0;font-size:18px;">Weather HUD Settings</h2>
- <button id="modal-close" style="background:none;border:0;color:#fff;font-size:18px;cursor:pointer;">✖</button>
- </div>
- <div style="margin-top:12px;display:grid;grid-template-columns:1fr 1fr;gap:12px;">
- <div style="grid-column:1/-1;">
- <label>Location:<br>
- <input id="opt-location" type="text" style="width:100%;padding:8px;margin-top:6px;border-radius:6px;border:1px solid #444;background:#222;color:#fff;">
- </label>
- </div>
- <div>
- <label><input id="opt-anim" type="checkbox"> Enable Animations</label><br><br>
- <label><input id="opt-forecast" type="checkbox"> Show 5-Day Forecast</label><br><br>
- <label>Update Interval (min):<br>
- <input id="opt-interval" type="number" min="1" value="${settings.updateIntervalMin}" style="width:120px;padding:6px;margin-top:6px;border-radius:6px;background:#222;color:#fff;border:1px solid #444;">
- </label>
- </div>
- <div>
- <label>Opacity:<br>
- <input id="opt-opacity" type="range" min="0.2" max="1" step="0.05" value="${settings.opacity}" style="width:100%;margin-top:6px;">
- </label><br><br>
- <label>Scale:<br>
- <input id="opt-scale" type="range" min="0.5" max="1.5" step="0.05" value="${settings.scale}" style="width:100%;margin-top:6px;">
- </label>
- </div>
- <div>
- <label>Theme:<br>
- <select id="opt-theme" style="width:100%;padding:8px;margin-top:6px;border-radius:6px;background:#222;color:#fff;border:1px solid #444;">
- <option value="auto">Auto</option>
- <option value="light">Light</option>
- <option value="dark">Dark</option>
- </select>
- </label><br><br>
- <label>Units:<br>
- <select id="opt-units" style="width:100%;padding:8px;margin-top:6px;border-radius:6px;background:#222;color:#fff;border:1px solid #444;">
- <option value="F">°F</option>
- <option value="C">°C</option>
- </select>
- </label>
- </div>
- </div>
- <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:16px;">
- <button id="opt-reset" style="padding:8px 12px;border-radius:6px;background:#333;color:#fff;border:0;cursor:pointer;">Reset</button>
- <button id="opt-cancel" style="padding:8px 12px;border-radius:6px;background:transparent;color:#fff;border:1px solid #555;cursor:pointer;">Cancel</button>
- <button id="opt-apply" style="padding:8px 12px;border-radius:6px;background:#1e90ff;color:#fff;border:0;cursor:pointer;">Apply</button>
- </div>
- <div style="margin-top:10px;font-size:12px;opacity:0.85;">
- Tip: Press <b>Ctrl/Cmd + O</b> to open settings. Press <b>Esc</b> to close.
- </div>
- `;
- modalBackdrop.appendChild(modalBox);
- document.body.appendChild(modalBackdrop);
- /* --------------------------
- ANIMATION ENGINE
- ---------------------------*/
- let particles = [];
- let clouds = [];
- let sunRotation = 0;
- let currentWeatherType = "";
- let animationsEnabled = !!settings.animationsEnabled;
- let animRunning = false;
- function createParticles(type){
- particles = [];
- const count = type==="snow" ? 60 : 50;
- for (let i=0;i<count;i++){
- particles.push({
- x: Math.random()*canvas.width,
- y: Math.random()*canvas.height,
- r: type==="snow" ? Math.random()*3+1 : 2,
- speedY: type==="snow" ? Math.random()*1+0.5 : Math.random()*3+2
- });
- }
- }
- function createClouds(){
- clouds = [];
- for (let i=0;i<5;i++){
- clouds.push({
- x: Math.random()*canvas.width,
- y: Math.random()*canvas.height/2,
- w: 60+Math.random()*40,
- h: 20+Math.random()*10,
- speed: 0.3+Math.random()*0.7
- });
- }
- }
- function animate(){
- if (!animationsEnabled){
- ctx.clearRect(0,0,canvas.width,canvas.height);
- animRunning = false;
- return;
- }
- animRunning = true;
- ctx.clearRect(0,0,canvas.width,canvas.height);
- // Sun rays
- if (currentWeatherType==="sun"){
- ctx.save();
- ctx.translate(canvas.width-50,50);
- sunRotation += 0.01;
- for (let i=0;i<12;i++){
- ctx.rotate(Math.PI/6);
- ctx.beginPath();
- ctx.moveTo(0,0);
- ctx.lineTo(0,20);
- ctx.strokeStyle = "rgba(255,255,0,0.6)";
- ctx.lineWidth = 2;
- ctx.stroke();
- }
- ctx.restore();
- }
- // Clouds
- if (currentWeatherType==="cloud"){
- clouds.forEach(c=>{
- ctx.beginPath();
- ctx.fillStyle = "rgba(255,255,255,0.6)";
- ctx.ellipse(c.x,c.y,c.w,c.h,0,0,Math.PI*2);
- ctx.fill();
- c.x += c.speed;
- if (c.x - c.w > canvas.width) c.x = -c.w;
- });
- }
- // Rain/Snow
- particles.forEach(p=>{
- ctx.beginPath();
- ctx.fillStyle = "rgba(255,255,255,0.85)";
- ctx.arc(p.x,p.y,p.r,0,Math.PI*2);
- ctx.fill();
- p.y += p.speedY;
- if (p.y > canvas.height) p.y = -10;
- });
- requestAnimationFrame(animate);
- }
- /* --------------------------
- CANVAS SIZING
- ---------------------------*/
- function resizeCanvas(){
- canvas.width = hud.clientWidth;
- canvas.height = hud.clientHeight;
- }
- window.addEventListener("resize", resizeCanvas);
- resizeCanvas();
- /* --------------------------
- WEATHER FETCH + UPDATE
- ---------------------------*/
- async function fetchWeather(){
- try {
- const res = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=j1`);
- const data = await res.json();
- const curr = data.current_condition[0];
- const temp = fmtTempFromF(curr.temp_F, settings.units);
- const desc = curr.weatherDesc[0].value.toLowerCase();
- const humidity = curr.humidity;
- const wind = curr.windspeedMiles;
- let icon = "🌈";
- for (let k in icons){ if (desc.includes(k)) { icon = icons[k]; break; } }
- // HUD text
- forecast.innerHTML = `${icon} ${desc},
- <span style="color:${tempColor(temp.num,settings.units)}">${temp.text}</span>,
- Humidity: ${humidity}%, Wind: ${wind} mph`;
- hud.style.background = weatherGradient(desc,temp.num,settings.units);
- // Animation type detection
- if (desc.includes("rain") || desc.includes("drizzle")){
- currentWeatherType = "rain";
- createParticles("rain");
- } else if (desc.includes("snow")){
- currentWeatherType = "snow";
- createParticles("snow");
- } else if (desc.includes("sunny") || desc.includes("clear")){
- currentWeatherType = "sun";
- particles = [];
- } else if (desc.includes("cloud") || desc.includes("overcast") || desc.includes("fog") || desc.includes("mist")){
- currentWeatherType = "cloud";
- particles = [];
- createClouds();
- } else {
- currentWeatherType = "";
- particles = [];
- }
- if (animationsEnabled && !animRunning) animate();
- if (!animationsEnabled){
- ctx.clearRect(0,0,canvas.width,canvas.height);
- particles = [];
- clouds = [];
- animRunning = false;
- }
- timeEl.textContent = new Date().toLocaleString();
- /* ---- 5-Day Forecast ----*/
- fiveDay.innerHTML = "";
- const days = (data.weather || []).slice(0,5);
- days.forEach(day=>{
- const div = document.createElement("div");
- div.style.flex = "1 1 50px";
- div.style.margin = "2px";
- div.style.padding = "6px";
- div.style.borderRadius = "5px";
- div.style.background = "rgba(255,255,255,0.12)";
- const date = new Date(day.date);
- const dayName = date.toLocaleDateString(undefined,{weekday:"short"});
- const tempsF = day.hourly.map(h=>parseFloat(h.tempF));
- const temps = tempsF.map(f=> settings.units==="F" ? Math.round(f) : Math.round((f-32)*5/9));
- const min = Math.min(...temps);
- const max = Math.max(...temps);
- let weatherMap = {};
- day.hourly.forEach(h=>{
- let d = h.weatherDesc[0].value.toLowerCase();
- weatherMap[d] = (weatherMap[d]||0)+1;
- });
- const mc = Object.entries(weatherMap).sort((a,b)=>b[1]-a[1])[0];
- let dIcon = "🌈";
- if (mc) {
- for (let k in icons) if (mc[0].includes(k)) dIcon = icons[k];
- }
- const avgWind = (day.hourly.reduce((a,h)=>a+parseFloat(h.windspeedMiles),0)/day.hourly.length).toFixed(0);
- const avgHum = (day.hourly.reduce((a,h)=>a+parseFloat(h.humidity),0)/day.hourly.length).toFixed(0);
- const avgRain = (day.hourly.reduce((a,h)=>a+parseFloat(h.chanceofrain),0)/day.hourly.length).toFixed(0);
- const u = settings.units==="F" ? "°F" : "°C";
- div.innerHTML =
- `<strong>${dayName}</strong><br>${dIcon}<br>
- <span style="color:${tempColor(max,settings.units)}">${max}${u}</span>/<span style="color:${tempColor(min,settings.units)}">${min}${u}</span><br>
- Wind: ${avgWind} mph<br>
- Humidity: ${avgHum}%<br>
- Rain: ${avgRain}%`;
- fiveDay.appendChild(div);
- });
- } catch(e){
- forecast.textContent = "Error fetching weather";
- fiveDay.innerHTML = "";
- console.error(e);
- }
- }
- let autoUpdate = null;
- function startAutoUpdate(){
- if (autoUpdate) clearInterval(autoUpdate);
- autoUpdate = setInterval(fetchWeather, (settings.updateIntervalMin||10)*60000);
- }
- startAutoUpdate();
- fetchWeather();
- /* --------------------------
- DRAGGABLE HUD
- ---------------------------*/
- let dragging=false,ox,oy;
- titleBar.addEventListener("mousedown",e=>{
- if (e.button!==0) return;
- dragging=true;
- ox=e.clientX-hud.offsetLeft;
- oy=e.clientY-hud.offsetTop;
- hud.style.transition="none";
- });
- document.addEventListener("mousemove",e=>{
- if (dragging){
- hud.style.left = Math.max(0,Math.min(e.clientX-ox,window.innerWidth-hud.offsetWidth))+"px";
- hud.style.top = Math.max(0,Math.min(e.clientY-oy,window.innerHeight-hud.offsetHeight))+"px";
- resizeCanvas();
- }
- });
- document.addEventListener("mouseup",()=>{
- if (dragging){
- dragging=false;
- hud.style.transition="opacity .3s, transform .15s, width .25s, background .4s";
- settings.left=hud.style.left;
- settings.top=hud.style.top;
- saveSettings();
- }
- });
- /* --------------------------
- RESIZING
- ---------------------------*/
- let resizing=false, startW, startX;
- resizeHandle.addEventListener("mousedown",e=>{
- if (e.button!==0) return;
- resizing=true;
- startW=hud.offsetWidth;
- startX=e.clientX;
- e.stopPropagation();
- hud.style.transition="none";
- });
- document.addEventListener("mousemove",e=>{
- if (resizing){
- let nw = Math.max(180,startW+(e.clientX-startX));
- hud.style.width = nw+"px";
- resizeCanvas();
- }
- });
- document.addEventListener("mouseup",()=>{
- if (resizing){
- resizing=false;
- hud.style.transition="opacity .3s, transform .15s, width .25s, background .4s";
- settings.width = hud.style.width;
- saveSettings();
- }
- });
- if (settings.left) hud.style.left=settings.left;
- if (settings.top) hud.style.top=settings.top;
- if (settings.width) hud.style.width=settings.width;
- /* --------------------------
- MODAL HANDLERS
- ---------------------------*/
- function preloadModal(){
- document.getElementById("opt-location").value = city;
- document.getElementById("opt-anim").checked = !!settings.animationsEnabled;
- document.getElementById("opt-forecast").checked = !!settings.showForecast;
- document.getElementById("opt-interval").value = settings.updateIntervalMin;
- document.getElementById("opt-opacity").value = settings.opacity;
- document.getElementById("opt-scale").value = settings.scale;
- document.getElementById("opt-theme").value = settings.theme;
- document.getElementById("opt-units").value = settings.units;
- }
- function openModal(){
- preloadModal();
- modalBackdrop.style.display="flex";
- requestAnimationFrame(()=>{
- modalBackdrop.style.opacity="1";
- modalBox.style.transform="scale(1)";
- });
- }
- function closeModal(){
- modalBackdrop.style.opacity="0";
- modalBox.style.transform="scale(0.96)";
- setTimeout(()=>modalBackdrop.style.display="none",180);
- }
- optionsBtn.addEventListener("click",openModal);
- modalBackdrop.querySelector("#modal-close").addEventListener("click",closeModal);
- modalBackdrop.addEventListener("click",e=>{ if(e.target===modalBackdrop) closeModal(); });
- document.getElementById("opt-cancel").addEventListener("click",closeModal);
- document.getElementById("opt-reset").addEventListener("click",()=>{
- settings = Object.assign({},DEFAULTS);
- city = settings.city;
- title.textContent = city+" Weather";
- hud.style.opacity=settings.opacity;
- hud.style.transform=`scale(${settings.scale})`;
- saveSettings();
- preloadModal();
- fetchWeather();
- });
- document.getElementById("opt-apply").addEventListener("click",()=>{
- const loc = document.getElementById("opt-location").value.trim();
- const anim = document.getElementById("opt-anim").checked;
- const fc = document.getElementById("opt-forecast").checked;
- const interval = parseInt(document.getElementById("opt-interval").value);
- const opacity = parseFloat(document.getElementById("opt-opacity").value);
- const scale = parseFloat(document.getElementById("opt-scale").value);
- const theme = document.getElementById("opt-theme").value;
- const units = document.getElementById("opt-units").value;
- if (loc){
- city=loc;
- settings.city=loc;
- title.textContent = city+" Weather";
- fetchWeather();
- }
- settings.animationsEnabled = anim;
- settings.showForecast = fc;
- settings.updateIntervalMin = interval >=1 ? interval : 10;
- settings.opacity = opacity;
- settings.scale = scale;
- settings.theme = theme;
- settings.units = units;
- hud.style.opacity=settings.opacity;
- hud.style.transform=`scale(${settings.scale})`;
- fiveDay.style.display = settings.showForecast ? "flex":"none";
- animationsEnabled = anim;
- if (anim && !animRunning) animate();
- if (!anim){
- particles=[];
- clouds=[];
- ctx.clearRect(0,0,canvas.width,canvas.height);
- animRunning=false;
- }
- applyTheme(settings.theme);
- startAutoUpdate();
- saveSettings();
- closeModal();
- });
- document.addEventListener("keydown",e=>{
- if (e.key==="Escape") closeModal();
- if ((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==="o"){
- e.preventDefault();
- openModal();
- }
- });
- /* --------------------------
- CLOSE HUD
- ---------------------------*/
- closeBtn.addEventListener("click",()=>{
- hud.style.opacity="0";
- hud.style.transform="scale(0.9)";
- setTimeout(()=>hud.remove(),300);
- });
- title.addEventListener("click",()=>{
- const val = prompt("Enter new location:",city);
- if(val){
- city = val.trim();
- settings.city = city;
- title.textContent = city+" Weather";
- saveSettings();
- fetchWeather();
- }
- });
- /* --------------------------
- THEME SYSTEM
- ---------------------------*/
- function applyTheme(theme){
- const dark = (theme==="auto")
- ? window.matchMedia("(prefers-color-scheme: dark)").matches
- : theme==="dark";
- if (dark){
- hud.style.color="#fff";
- modalBox.style.background="rgba(0,0,0,0.95)";
- } else {
- hud.style.color="#000";
- modalBox.style.background="rgba(255,255,255,0.95)";
- }
- }
- applyTheme(settings.theme);
- if (window.matchMedia){
- const mq = window.matchMedia("(prefers-color-scheme: dark)");
- mq.addEventListener("change",()=>{ if(settings.theme==="auto") applyTheme("auto"); });
- }
- resizeCanvas();
- saveSettings();
- })();
Advertisement