smoothretro82

Weather forecast HUD v 0.6

Dec 10th, 2025 (edited)
55
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 25.90 KB | None | 0 0
  1. /* WEATHER HUD — WITH MODAL OPTIONS WINDOW (Option C)
  2. Fully integrated script, ready to paste.
  3. Includes: options button, modal, theme, units, animations, update interval, opacity, scale, location, 5-day forecast.
  4. */
  5. (function() {
  6. /* --------------------------
  7. CONFIG + DEFAULT SETTINGS
  8. ---------------------------*/
  9. let city = "Greenfield,OH";
  10. const DEFAULTS = {
  11. city,
  12. updateIntervalMin: 10,
  13. animationsEnabled: true,
  14. showForecast: true,
  15. opacity: 1,
  16. scale: 1,
  17. theme: "auto",
  18. units: "F",
  19. left: null,
  20. top: null,
  21. width: null
  22. };
  23. const STORAGE_KEY = "weatherHudSettings_v1";
  24.  
  25. let settings = Object.assign({}, DEFAULTS);
  26. try {
  27. const saved = JSON.parse(localStorage.getItem(STORAGE_KEY));
  28. if (saved && typeof saved === "object") settings = Object.assign(settings, saved);
  29. } catch(e){}
  30.  
  31. city = settings.city || city;
  32.  
  33. const icons = {
  34. "sunny": "☀️",
  35. "clear": "☀️",
  36. "cloud": "☁️",
  37. "overcast": "☁️",
  38. "rain": "🌧️",
  39. "drizzle": "🌦️",
  40. "snow": "❄️",
  41. "thunder": "⛈️",
  42. "fog": "🌫️",
  43. "mist": "🌫️"
  44. };
  45.  
  46. function saveSettings(){
  47. try {
  48. localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
  49. } catch(e){}
  50. }
  51.  
  52. function tempColor(temp, units){
  53. if (isNaN(temp)) return "#fff";
  54. let tF = units === "F" ? temp : (temp*9/5)+32;
  55. if (tF <= 32) return "#00f";
  56. if (tF <= 60) return "#0ff";
  57. if (tF <= 80) return "#ff0";
  58. return "#f00";
  59. }
  60.  
  61. function weatherGradient(desc, temp, units){
  62. desc = (desc||"").toLowerCase();
  63. let base;
  64. if (desc.includes("sunny") || desc.includes("clear")) base = "linear-gradient(135deg,#FFD700,#FFA500)";
  65. else if (desc.includes("cloud") || desc.includes("overcast")) base = "linear-gradient(135deg,#B0C4DE,#778899)";
  66. else if (desc.includes("rain") || desc.includes("drizzle")) base = "linear-gradient(135deg,#1E90FF,#00008B)";
  67. else if (desc.includes("thunder")) base = "linear-gradient(135deg,#4B0082,#2F4F4F)";
  68. else if (desc.includes("snow")) base = "linear-gradient(135deg,#E0FFFF,#87CEFA)";
  69. else if (desc.includes("fog") || desc.includes("mist")) base = "linear-gradient(135deg,#D3D3D3,#A9A9A9)";
  70. else base = "linear-gradient(135deg,#FFD700,#FF8C00)";
  71.  
  72. const tF = units==="F" ? temp : (temp*9/5)+32;
  73.  
  74. if (tF !== null && tF <= 32) base += ",rgba(0,0,255,0.12)";
  75. else if (tF !== null && tF >= 90) base += ",rgba(255,0,0,0.12)";
  76.  
  77. return base;
  78. }
  79.  
  80. function fmtTempFromF(F, units){
  81. const v = parseFloat(F);
  82. if (isNaN(v)) return {num:NaN,text:F};
  83. if (units==="F") return {num:Math.round(v), text:`${Math.round(v)}°F`};
  84. const c = Math.round((v-32)*5/9);
  85. return {num:c,text:`${c}°C`};
  86. }
  87.  
  88. /* --------------------------
  89. HUD BASE
  90. ---------------------------*/
  91. const hud = document.createElement("div");
  92. hud.style.position = "fixed";
  93. hud.style.top = "10px";
  94. hud.style.right = "10px";
  95. hud.style.background = "rgba(0,0,0,0.6)";
  96. hud.style.color = "#fff";
  97. hud.style.padding = "15px 20px";
  98. hud.style.borderRadius = "12px";
  99. hud.style.fontFamily = "Arial, sans-serif";
  100. hud.style.zIndex = "9999";
  101. hud.style.width = "min(90vw,320px)";
  102. hud.style.textAlign = "center";
  103. hud.style.boxShadow = "0 0 20px rgba(0,0,0,0.7)";
  104. hud.style.cursor = "move";
  105. hud.style.userSelect = "none";
  106. hud.style.opacity = settings.opacity;
  107. hud.style.transform = `scale(${settings.scale})`;
  108. hud.style.transition = "opacity .3s, transform .15s, width .25s, background .4s";
  109.  
  110. const canvas = document.createElement("canvas");
  111. canvas.style.position = "absolute";
  112. canvas.style.inset = "0";
  113. canvas.style.width = "100%";
  114. canvas.style.height = "100%";
  115. canvas.style.pointerEvents = "none";
  116. hud.appendChild(canvas);
  117. const ctx = canvas.getContext("2d");
  118.  
  119. const closeBtn = document.createElement("span");
  120. closeBtn.textContent = "✖";
  121. closeBtn.style.position = "absolute";
  122. closeBtn.style.top = "5px";
  123. closeBtn.style.right = "10px";
  124. closeBtn.style.cursor = "pointer";
  125. closeBtn.style.fontWeight = "bold";
  126. closeBtn.title = "Close HUD";
  127. hud.appendChild(closeBtn);
  128.  
  129. const optionsBtn = document.createElement("span");
  130. optionsBtn.textContent = "⚙";
  131. optionsBtn.style.position = "absolute";
  132. optionsBtn.style.top = "5px";
  133. optionsBtn.style.left = "10px";
  134. optionsBtn.style.cursor = "pointer";
  135. optionsBtn.style.fontWeight = "bold";
  136. optionsBtn.title = "Options";
  137. hud.appendChild(optionsBtn);
  138.  
  139. const titleBar = document.createElement("div");
  140. titleBar.style.background = "rgba(0,0,0,0.85)";
  141. titleBar.style.padding = "5px 0";
  142. titleBar.style.borderRadius = "8px 8px 0 0";
  143. titleBar.style.marginBottom = "8px";
  144. titleBar.style.cursor = "move";
  145. titleBar.style.display = "flex";
  146. titleBar.style.justifyContent = "center";
  147.  
  148. const title = document.createElement("h3");
  149. title.textContent = city + " Weather";
  150. title.style.margin = "0";
  151. title.style.fontSize = "16px";
  152. title.style.textDecoration = "underline";
  153. title.title = "Click to change location";
  154. titleBar.appendChild(title);
  155.  
  156. hud.appendChild(titleBar);
  157.  
  158. const forecast = document.createElement("p");
  159. forecast.style.margin = "0";
  160. forecast.style.fontSize = "14px";
  161.  
  162. const timeEl = document.createElement("p");
  163. timeEl.style.margin = "5px 0 10px";
  164.  
  165. const fiveDay = document.createElement("div");
  166. fiveDay.style.display = settings.showForecast ? "flex" : "none";
  167. fiveDay.style.justifyContent = "space-between";
  168. fiveDay.style.flexWrap = "wrap";
  169. fiveDay.style.marginTop = "10px";
  170. fiveDay.style.fontSize = "12px";
  171.  
  172. hud.appendChild(forecast);
  173. hud.appendChild(timeEl);
  174. hud.appendChild(fiveDay);
  175.  
  176. document.body.appendChild(hud);
  177.  
  178. /* --------------------------
  179. RESIZE HANDLE
  180. ---------------------------*/
  181. const resizeHandle = document.createElement("div");
  182. resizeHandle.style.position = "absolute";
  183. resizeHandle.style.right = "6px";
  184. resizeHandle.style.bottom = "6px";
  185. resizeHandle.style.width = "16px";
  186. resizeHandle.style.height = "16px";
  187. resizeHandle.style.cursor = "nwse-resize";
  188. resizeHandle.style.opacity = "0.6";
  189. hud.appendChild(resizeHandle);
  190.  
  191. /* --------------------------
  192. MODAL OPTIONS (OPTION C)
  193. ---------------------------*/
  194. const modalBackdrop = document.createElement("div");
  195. modalBackdrop.style.position = "fixed";
  196. modalBackdrop.style.inset = "0";
  197. modalBackdrop.style.background = "rgba(0,0,0,0.35)";
  198. modalBackdrop.style.backdropFilter = "blur(4px)";
  199. modalBackdrop.style.zIndex = "10010";
  200. modalBackdrop.style.display = "none";
  201. modalBackdrop.style.justifyContent = "center";
  202. modalBackdrop.style.alignItems = "center";
  203. modalBackdrop.style.transition = "opacity 0.18s";
  204.  
  205. const modalBox = document.createElement("div");
  206. modalBox.style.width = "min(90vw,560px)";
  207. modalBox.style.maxHeight = "80vh";
  208. modalBox.style.overflow = "auto";
  209. modalBox.style.background = "rgba(0,0,0,0.95)";
  210. modalBox.style.borderRadius = "12px";
  211. modalBox.style.padding = "18px";
  212. modalBox.style.boxShadow = "0 20px 60px rgba(0,0,0,0.6)";
  213. modalBox.style.transform = "scale(0.96)";
  214. modalBox.style.transition = "transform .2s, opacity .18s";
  215. modalBox.style.color = "#fff";
  216.  
  217. modalBox.innerHTML = `
  218. <div style="display:flex;justify-content:space-between;align-items:center;">
  219. <h2 style="margin:0;font-size:18px;">Weather HUD Settings</h2>
  220. <button id="modal-close" style="background:none;border:0;color:#fff;font-size:18px;cursor:pointer;">✖</button>
  221. </div>
  222.  
  223. <div style="margin-top:12px;display:grid;grid-template-columns:1fr 1fr;gap:12px;">
  224. <div style="grid-column:1/-1;">
  225. <label>Location:<br>
  226. <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;">
  227. </label>
  228. </div>
  229.  
  230. <div>
  231. <label><input id="opt-anim" type="checkbox"> Enable Animations</label><br><br>
  232. <label><input id="opt-forecast" type="checkbox"> Show 5-Day Forecast</label><br><br>
  233. <label>Update Interval (min):<br>
  234. <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;">
  235. </label>
  236. </div>
  237.  
  238. <div>
  239. <label>Opacity:<br>
  240. <input id="opt-opacity" type="range" min="0.2" max="1" step="0.05" value="${settings.opacity}" style="width:100%;margin-top:6px;">
  241. </label><br><br>
  242. <label>Scale:<br>
  243. <input id="opt-scale" type="range" min="0.5" max="1.5" step="0.05" value="${settings.scale}" style="width:100%;margin-top:6px;">
  244. </label>
  245. </div>
  246.  
  247. <div>
  248. <label>Theme:<br>
  249. <select id="opt-theme" style="width:100%;padding:8px;margin-top:6px;border-radius:6px;background:#222;color:#fff;border:1px solid #444;">
  250. <option value="auto">Auto</option>
  251. <option value="light">Light</option>
  252. <option value="dark">Dark</option>
  253. </select>
  254. </label><br><br>
  255. <label>Units:<br>
  256. <select id="opt-units" style="width:100%;padding:8px;margin-top:6px;border-radius:6px;background:#222;color:#fff;border:1px solid #444;">
  257. <option value="F">°F</option>
  258. <option value="C">°C</option>
  259. </select>
  260. </label>
  261. </div>
  262. </div>
  263.  
  264. <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:16px;">
  265. <button id="opt-reset" style="padding:8px 12px;border-radius:6px;background:#333;color:#fff;border:0;cursor:pointer;">Reset</button>
  266. <button id="opt-cancel" style="padding:8px 12px;border-radius:6px;background:transparent;color:#fff;border:1px solid #555;cursor:pointer;">Cancel</button>
  267. <button id="opt-apply" style="padding:8px 12px;border-radius:6px;background:#1e90ff;color:#fff;border:0;cursor:pointer;">Apply</button>
  268. </div>
  269.  
  270. <div style="margin-top:10px;font-size:12px;opacity:0.85;">
  271. Tip: Press <b>Ctrl/Cmd + O</b> to open settings. Press <b>Esc</b> to close.
  272. </div>
  273. `;
  274.  
  275. modalBackdrop.appendChild(modalBox);
  276. document.body.appendChild(modalBackdrop);
  277.  
  278. /* --------------------------
  279. ANIMATION ENGINE
  280. ---------------------------*/
  281. let particles = [];
  282. let clouds = [];
  283. let sunRotation = 0;
  284. let currentWeatherType = "";
  285. let animationsEnabled = !!settings.animationsEnabled;
  286. let animRunning = false;
  287.  
  288. function createParticles(type){
  289. particles = [];
  290. const count = type==="snow" ? 60 : 50;
  291. for (let i=0;i<count;i++){
  292. particles.push({
  293. x: Math.random()*canvas.width,
  294. y: Math.random()*canvas.height,
  295. r: type==="snow" ? Math.random()*3+1 : 2,
  296. speedY: type==="snow" ? Math.random()*1+0.5 : Math.random()*3+2
  297. });
  298. }
  299. }
  300.  
  301. function createClouds(){
  302. clouds = [];
  303. for (let i=0;i<5;i++){
  304. clouds.push({
  305. x: Math.random()*canvas.width,
  306. y: Math.random()*canvas.height/2,
  307. w: 60+Math.random()*40,
  308. h: 20+Math.random()*10,
  309. speed: 0.3+Math.random()*0.7
  310. });
  311. }
  312. }
  313.  
  314. function animate(){
  315. if (!animationsEnabled){
  316. ctx.clearRect(0,0,canvas.width,canvas.height);
  317. animRunning = false;
  318. return;
  319. }
  320. animRunning = true;
  321.  
  322. ctx.clearRect(0,0,canvas.width,canvas.height);
  323.  
  324. // Sun rays
  325. if (currentWeatherType==="sun"){
  326. ctx.save();
  327. ctx.translate(canvas.width-50,50);
  328. sunRotation += 0.01;
  329.  
  330. for (let i=0;i<12;i++){
  331. ctx.rotate(Math.PI/6);
  332. ctx.beginPath();
  333. ctx.moveTo(0,0);
  334. ctx.lineTo(0,20);
  335. ctx.strokeStyle = "rgba(255,255,0,0.6)";
  336. ctx.lineWidth = 2;
  337. ctx.stroke();
  338. }
  339. ctx.restore();
  340. }
  341.  
  342. // Clouds
  343. if (currentWeatherType==="cloud"){
  344. clouds.forEach(c=>{
  345. ctx.beginPath();
  346. ctx.fillStyle = "rgba(255,255,255,0.6)";
  347. ctx.ellipse(c.x,c.y,c.w,c.h,0,0,Math.PI*2);
  348. ctx.fill();
  349. c.x += c.speed;
  350. if (c.x - c.w > canvas.width) c.x = -c.w;
  351. });
  352. }
  353.  
  354. // Rain/Snow
  355. particles.forEach(p=>{
  356. ctx.beginPath();
  357. ctx.fillStyle = "rgba(255,255,255,0.85)";
  358. ctx.arc(p.x,p.y,p.r,0,Math.PI*2);
  359. ctx.fill();
  360. p.y += p.speedY;
  361. if (p.y > canvas.height) p.y = -10;
  362. });
  363.  
  364. requestAnimationFrame(animate);
  365. }
  366.  
  367. /* --------------------------
  368. CANVAS SIZING
  369. ---------------------------*/
  370. function resizeCanvas(){
  371. canvas.width = hud.clientWidth;
  372. canvas.height = hud.clientHeight;
  373. }
  374. window.addEventListener("resize", resizeCanvas);
  375. resizeCanvas();
  376.  
  377. /* --------------------------
  378. WEATHER FETCH + UPDATE
  379. ---------------------------*/
  380. async function fetchWeather(){
  381. try {
  382. const res = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=j1`);
  383. const data = await res.json();
  384.  
  385. const curr = data.current_condition[0];
  386. const temp = fmtTempFromF(curr.temp_F, settings.units);
  387. const desc = curr.weatherDesc[0].value.toLowerCase();
  388. const humidity = curr.humidity;
  389. const wind = curr.windspeedMiles;
  390.  
  391. let icon = "🌈";
  392. for (let k in icons){ if (desc.includes(k)) { icon = icons[k]; break; } }
  393.  
  394. // HUD text
  395. forecast.innerHTML = `${icon} ${desc},
  396. <span style="color:${tempColor(temp.num,settings.units)}">${temp.text}</span>,
  397. Humidity: ${humidity}%, Wind: ${wind} mph`;
  398.  
  399. hud.style.background = weatherGradient(desc,temp.num,settings.units);
  400.  
  401. // Animation type detection
  402. if (desc.includes("rain") || desc.includes("drizzle")){
  403. currentWeatherType = "rain";
  404. createParticles("rain");
  405. } else if (desc.includes("snow")){
  406. currentWeatherType = "snow";
  407. createParticles("snow");
  408. } else if (desc.includes("sunny") || desc.includes("clear")){
  409. currentWeatherType = "sun";
  410. particles = [];
  411. } else if (desc.includes("cloud") || desc.includes("overcast") || desc.includes("fog") || desc.includes("mist")){
  412. currentWeatherType = "cloud";
  413. particles = [];
  414. createClouds();
  415. } else {
  416. currentWeatherType = "";
  417. particles = [];
  418. }
  419.  
  420. if (animationsEnabled && !animRunning) animate();
  421. if (!animationsEnabled){
  422. ctx.clearRect(0,0,canvas.width,canvas.height);
  423. particles = [];
  424. clouds = [];
  425. animRunning = false;
  426. }
  427.  
  428. timeEl.textContent = new Date().toLocaleString();
  429.  
  430. /* ---- 5-Day Forecast ----*/
  431. fiveDay.innerHTML = "";
  432. const days = (data.weather || []).slice(0,5);
  433.  
  434. days.forEach(day=>{
  435. const div = document.createElement("div");
  436. div.style.flex = "1 1 50px";
  437. div.style.margin = "2px";
  438. div.style.padding = "6px";
  439. div.style.borderRadius = "5px";
  440. div.style.background = "rgba(255,255,255,0.12)";
  441.  
  442. const date = new Date(day.date);
  443. const dayName = date.toLocaleDateString(undefined,{weekday:"short"});
  444.  
  445. const tempsF = day.hourly.map(h=>parseFloat(h.tempF));
  446. const temps = tempsF.map(f=> settings.units==="F" ? Math.round(f) : Math.round((f-32)*5/9));
  447.  
  448. const min = Math.min(...temps);
  449. const max = Math.max(...temps);
  450.  
  451. let weatherMap = {};
  452. day.hourly.forEach(h=>{
  453. let d = h.weatherDesc[0].value.toLowerCase();
  454. weatherMap[d] = (weatherMap[d]||0)+1;
  455. });
  456. const mc = Object.entries(weatherMap).sort((a,b)=>b[1]-a[1])[0];
  457. let dIcon = "🌈";
  458. if (mc) {
  459. for (let k in icons) if (mc[0].includes(k)) dIcon = icons[k];
  460. }
  461.  
  462. const avgWind = (day.hourly.reduce((a,h)=>a+parseFloat(h.windspeedMiles),0)/day.hourly.length).toFixed(0);
  463. const avgHum = (day.hourly.reduce((a,h)=>a+parseFloat(h.humidity),0)/day.hourly.length).toFixed(0);
  464. const avgRain = (day.hourly.reduce((a,h)=>a+parseFloat(h.chanceofrain),0)/day.hourly.length).toFixed(0);
  465.  
  466. const u = settings.units==="F" ? "°F" : "°C";
  467.  
  468. div.innerHTML =
  469. `<strong>${dayName}</strong><br>${dIcon}<br>
  470. <span style="color:${tempColor(max,settings.units)}">${max}${u}</span>/<span style="color:${tempColor(min,settings.units)}">${min}${u}</span><br>
  471. Wind: ${avgWind} mph<br>
  472. Humidity: ${avgHum}%<br>
  473. Rain: ${avgRain}%`;
  474.  
  475. fiveDay.appendChild(div);
  476. });
  477.  
  478. } catch(e){
  479. forecast.textContent = "Error fetching weather";
  480. fiveDay.innerHTML = "";
  481. console.error(e);
  482. }
  483. }
  484.  
  485. let autoUpdate = null;
  486. function startAutoUpdate(){
  487. if (autoUpdate) clearInterval(autoUpdate);
  488. autoUpdate = setInterval(fetchWeather, (settings.updateIntervalMin||10)*60000);
  489. }
  490.  
  491. startAutoUpdate();
  492. fetchWeather();
  493.  
  494. /* --------------------------
  495. DRAGGABLE HUD
  496. ---------------------------*/
  497. let dragging=false,ox,oy;
  498. titleBar.addEventListener("mousedown",e=>{
  499. if (e.button!==0) return;
  500. dragging=true;
  501. ox=e.clientX-hud.offsetLeft;
  502. oy=e.clientY-hud.offsetTop;
  503. hud.style.transition="none";
  504. });
  505. document.addEventListener("mousemove",e=>{
  506. if (dragging){
  507. hud.style.left = Math.max(0,Math.min(e.clientX-ox,window.innerWidth-hud.offsetWidth))+"px";
  508. hud.style.top = Math.max(0,Math.min(e.clientY-oy,window.innerHeight-hud.offsetHeight))+"px";
  509. resizeCanvas();
  510. }
  511. });
  512. document.addEventListener("mouseup",()=>{
  513. if (dragging){
  514. dragging=false;
  515. hud.style.transition="opacity .3s, transform .15s, width .25s, background .4s";
  516. settings.left=hud.style.left;
  517. settings.top=hud.style.top;
  518. saveSettings();
  519. }
  520. });
  521.  
  522. /* --------------------------
  523. RESIZING
  524. ---------------------------*/
  525. let resizing=false, startW, startX;
  526. resizeHandle.addEventListener("mousedown",e=>{
  527. if (e.button!==0) return;
  528. resizing=true;
  529. startW=hud.offsetWidth;
  530. startX=e.clientX;
  531. e.stopPropagation();
  532. hud.style.transition="none";
  533. });
  534. document.addEventListener("mousemove",e=>{
  535. if (resizing){
  536. let nw = Math.max(180,startW+(e.clientX-startX));
  537. hud.style.width = nw+"px";
  538. resizeCanvas();
  539. }
  540. });
  541. document.addEventListener("mouseup",()=>{
  542. if (resizing){
  543. resizing=false;
  544. hud.style.transition="opacity .3s, transform .15s, width .25s, background .4s";
  545. settings.width = hud.style.width;
  546. saveSettings();
  547. }
  548. });
  549.  
  550. if (settings.left) hud.style.left=settings.left;
  551. if (settings.top) hud.style.top=settings.top;
  552. if (settings.width) hud.style.width=settings.width;
  553.  
  554. /* --------------------------
  555. MODAL HANDLERS
  556. ---------------------------*/
  557. function preloadModal(){
  558. document.getElementById("opt-location").value = city;
  559. document.getElementById("opt-anim").checked = !!settings.animationsEnabled;
  560. document.getElementById("opt-forecast").checked = !!settings.showForecast;
  561. document.getElementById("opt-interval").value = settings.updateIntervalMin;
  562. document.getElementById("opt-opacity").value = settings.opacity;
  563. document.getElementById("opt-scale").value = settings.scale;
  564. document.getElementById("opt-theme").value = settings.theme;
  565. document.getElementById("opt-units").value = settings.units;
  566. }
  567.  
  568. function openModal(){
  569. preloadModal();
  570. modalBackdrop.style.display="flex";
  571. requestAnimationFrame(()=>{
  572. modalBackdrop.style.opacity="1";
  573. modalBox.style.transform="scale(1)";
  574. });
  575. }
  576.  
  577. function closeModal(){
  578. modalBackdrop.style.opacity="0";
  579. modalBox.style.transform="scale(0.96)";
  580. setTimeout(()=>modalBackdrop.style.display="none",180);
  581. }
  582.  
  583. optionsBtn.addEventListener("click",openModal);
  584. modalBackdrop.querySelector("#modal-close").addEventListener("click",closeModal);
  585. modalBackdrop.addEventListener("click",e=>{ if(e.target===modalBackdrop) closeModal(); });
  586.  
  587. document.getElementById("opt-cancel").addEventListener("click",closeModal);
  588.  
  589. document.getElementById("opt-reset").addEventListener("click",()=>{
  590. settings = Object.assign({},DEFAULTS);
  591. city = settings.city;
  592. title.textContent = city+" Weather";
  593. hud.style.opacity=settings.opacity;
  594. hud.style.transform=`scale(${settings.scale})`;
  595. saveSettings();
  596. preloadModal();
  597. fetchWeather();
  598. });
  599.  
  600. document.getElementById("opt-apply").addEventListener("click",()=>{
  601. const loc = document.getElementById("opt-location").value.trim();
  602. const anim = document.getElementById("opt-anim").checked;
  603. const fc = document.getElementById("opt-forecast").checked;
  604. const interval = parseInt(document.getElementById("opt-interval").value);
  605. const opacity = parseFloat(document.getElementById("opt-opacity").value);
  606. const scale = parseFloat(document.getElementById("opt-scale").value);
  607. const theme = document.getElementById("opt-theme").value;
  608. const units = document.getElementById("opt-units").value;
  609.  
  610. if (loc){
  611. city=loc;
  612. settings.city=loc;
  613. title.textContent = city+" Weather";
  614. fetchWeather();
  615. }
  616.  
  617. settings.animationsEnabled = anim;
  618. settings.showForecast = fc;
  619. settings.updateIntervalMin = interval >=1 ? interval : 10;
  620. settings.opacity = opacity;
  621. settings.scale = scale;
  622. settings.theme = theme;
  623. settings.units = units;
  624.  
  625. hud.style.opacity=settings.opacity;
  626. hud.style.transform=`scale(${settings.scale})`;
  627. fiveDay.style.display = settings.showForecast ? "flex":"none";
  628.  
  629. animationsEnabled = anim;
  630. if (anim && !animRunning) animate();
  631. if (!anim){
  632. particles=[];
  633. clouds=[];
  634. ctx.clearRect(0,0,canvas.width,canvas.height);
  635. animRunning=false;
  636. }
  637.  
  638. applyTheme(settings.theme);
  639. startAutoUpdate();
  640. saveSettings();
  641. closeModal();
  642. });
  643.  
  644. document.addEventListener("keydown",e=>{
  645. if (e.key==="Escape") closeModal();
  646. if ((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==="o"){
  647. e.preventDefault();
  648. openModal();
  649. }
  650. });
  651.  
  652. /* --------------------------
  653. CLOSE HUD
  654. ---------------------------*/
  655. closeBtn.addEventListener("click",()=>{
  656. hud.style.opacity="0";
  657. hud.style.transform="scale(0.9)";
  658. setTimeout(()=>hud.remove(),300);
  659. });
  660.  
  661. title.addEventListener("click",()=>{
  662. const val = prompt("Enter new location:",city);
  663. if(val){
  664. city = val.trim();
  665. settings.city = city;
  666. title.textContent = city+" Weather";
  667. saveSettings();
  668. fetchWeather();
  669. }
  670. });
  671.  
  672. /* --------------------------
  673. THEME SYSTEM
  674. ---------------------------*/
  675. function applyTheme(theme){
  676. const dark = (theme==="auto")
  677. ? window.matchMedia("(prefers-color-scheme: dark)").matches
  678. : theme==="dark";
  679.  
  680. if (dark){
  681. hud.style.color="#fff";
  682. modalBox.style.background="rgba(0,0,0,0.95)";
  683. } else {
  684. hud.style.color="#000";
  685. modalBox.style.background="rgba(255,255,255,0.95)";
  686. }
  687. }
  688. applyTheme(settings.theme);
  689.  
  690. if (window.matchMedia){
  691. const mq = window.matchMedia("(prefers-color-scheme: dark)");
  692. mq.addEventListener("change",()=>{ if(settings.theme==="auto") applyTheme("auto"); });
  693. }
  694.  
  695. resizeCanvas();
  696. saveSettings();
  697. })();
  698.  
Advertisement
Comments
Add Comment
Please, Sign In to add comment