From 3d93ed254d91c3369a296c3d881f58e020eadd2f Mon Sep 17 00:00:00 2001 From: Austin Rooks Date: Thu, 17 Oct 2024 21:49:48 -0500 Subject: [PATCH] add modal explaining wave data --- assets/static/index.min.js | 2 +- assets/styles.css | 2 +- client/parsers/realtime.js | 5 ++ src/realtime.rs | 26 ++++++++- src/spot.rs | 17 ++++-- templates/base.html | 8 +-- templates/includes/latest.html | 99 ++++++++++++++++++++++++++++++++-- 7 files changed, 141 insertions(+), 18 deletions(-) diff --git a/assets/static/index.min.js b/assets/static/index.min.js index 799916d..da921ca 100644 --- a/assets/static/index.min.js +++ b/assets/static/index.min.js @@ -1 +1 @@ -!function(){"use strict";function e(e){if(null==e)throw"item is null or undefined";return e}function t(e){if(null==e)throw"item is null or undefined";if(e instanceof HTMLButtonElement)return e;throw"item is not a button"}function n(t,n){e(document.getElementById(t)).innerText=n}function a(e,t,n){document.getElementById(e)?.setAttribute(t,n)}function i(e,t){e instanceof Array?e.forEach((e=>{a(e,"style",t)})):a(e,"style",t)}function o(e){document.querySelectorAll(e).forEach((e=>e.remove()))}function l(e){document.getElementById(e)?.remove()}function r(e,t){document.getElementById(e)?.classList.remove(t)}function s(e){r(e,"hidden")}const c="#a8a29e",d={"#0bd674":"Good","#ffcd1e":"Fair to Good","#ff9500":"Poor","#f4496d":"Very Poor","#a8a29e":"Flat"};let u,g,p,m,f,w,v,y,h,x,b,_;function E(e){const a=new Date(e.starting_at).getHours();let E=new Date(e.starting_at).getHours();if(a>20){let t=24-a;const n=e.wave_height_labels.length-(e.wave_height_labels.length-t)%24;E=0,g=e.wave_height_labels.slice(t,n),u=e.quality.slice(t,n),p=e.wave_height.slice(t,n),m=e.wind_speed.slice(t,n),f=e.wind_direction.slice(t,n),w=e.wind_gust.slice(t,n),v=e.wave_period.slice(t,n),y=e.temperature.slice(t,n),h=e.dewpoint.slice(t,n),x=e.cloud_cover.slice(t,n),b=e.probability_of_precipitation.slice(t,n),_=e.probability_of_thunder.slice(t,n)}else{const t=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][new Date(e.starting_at).getDay()],n=[];for(let e=0;e=10&&e<12&&n.push(`${t} ${e} AM`),12===e&&n.push(`${t} ${e} PM`),e>12&&n.push(`${t} 0${e-12} PM`)):n.push(`${t} 12 AM`);const i=e.wave_height_labels.length+a-(e.wave_height_labels.length+a)%24;g=n.concat(e.wave_height_labels).slice(0,i),u=new Array(a).fill("#a8a29e").concat(e.quality).slice(0,i),p=new Array(a).fill(0).concat(e.wave_height).slice(0,i),m=new Array(a).fill(0).concat(e.wind_speed).slice(0,i),f=new Array(a).fill(0).concat(e.wind_direction).slice(0,i),w=new Array(a).fill(0).concat(e.wind_gust).slice(0,i),v=new Array(a).fill(0).concat(e.wave_period).slice(0,i),y=new Array(a).fill(0).concat(e.temperature).slice(0,i),h=new Array(a).fill(0).concat(e.dewpoint).slice(0,i),x=new Array(a).fill(0).concat(e.cloud_cover).slice(0,i),b=new Array(a).fill(0).concat(e.probability_of_precipitation).slice(0,i),_=new Array(a).fill(0).concat(e.probability_of_thunder).slice(0,i)}let A=(new Date).getHours();const I=document.getElementById("current-wave-height");""===I?.innerText&&(I.innerText=e.current_wave_height,"0"==e.current_wave_height&&(i("wave-quality",`background-color: ${c}`),n("wave-quality-text",d[c]),i("wave-quality-text",`color: ${c}`),o(".wave-quality-loader")),i("wave-icon",`transform: rotate(${e.current_wave_direction}deg);`),o(".wavey"));const q=document.getElementById("current-wave-period");""===q?.innerText&&(q.innerText=e.current_wave_period,l("wavey-period-loader")),n("legend-label",g[A]),n("legend-quality",d[u[A]]),n("legend-wave-height",p[A]),n("legend-wind-speed",m[A]),i("legend-wind-icon",`transform: rotate(${f[A]+180}deg);`),n("legend-wave-period",v[A]),n("legend-wind-gust",w[A]),n("forecast-as-of",`Updated ${e.as_of}`),n("forecast-as-of-2",`Updated ${e.as_of}`),o(".loader"),s("forecast"),s("wave-quality"),s("legend-container"),r("forecast-as-of-container","animate-pulse"),r("forecast-as-of-container-2","animate-pulse"),s("temperature-legend-container"),n("temperature-legend-label",g[A]),n("temperature-legend-temperature",y[A]),n("temperature-legend-dewpoint",h[A]),s("precipitation-legend-container"),n("precipitation-legend-label",g[A]),n("precipitation-legend-precipitation",b[A]),n("precipitation-legend-thunder",_[A]),n("precipitation-legend-cloud-cover",x[A]),i("legend",`background-color: ${u[A]};`),i(["legend-container","precipitation-legend-container","temperature-legend-container"],"margin-top: 1rem;");const T={id:"vert",defaults:{width:1,dash:[3,3]},afterInit:(e,t,n)=>{e.corsair={x:0,y:0}},afterDraw:(e,t,n)=>{if(e.tooltip?._active?.length){let t=e.tooltip._active[0].element.x,a=e.scales.y,i=e.ctx;i.save(),i.beginPath(),i.moveTo(t,a.top),i.lineTo(t,a.bottom),i.lineWidth=1,i.setLineDash(n.dash),i.strokeStyle="#fff",i.stroke(),i.restore()}if(A-E>C){const t=e.ctx,n=e.scales.x.getPixelForValue(A-E);t.save(),t.strokeStyle="#5b5b58",t.lineWidth=1,t.beginPath(),t.moveTo(n,0),t.lineTo(n,e.height),t.stroke(),t.fillStyle="#5b5b58",t.font="bold 1rem ui-sans-serif, system-ui, sans-serif",t.fillText("Now",A>14&&24===L?n-45:n+5,15),t.restore()}}};function $(e){let t;t=e>=g.length-a?g.length-a-1:e;B(t+(0===C?E:C))}function B(e){const t=u[e];i("legend",`background-color: ${t}`),n("legend-label",g[e]),n("legend-quality",d[t]),n("legend-wave-height",p[e]),n("legend-wind-speed",m[e]),i("legend-wind-icon",`transform: rotate(${f[e]+180}deg);`),n("legend-wave-period",v[e]),n("legend-wind-gust",w[e]),n("temperature-legend-label",g[e]),n("temperature-legend-temperature",y[e]),n("temperature-legend-dewpoint",h[e]),n("precipitation-legend-label",g[e]),n("precipitation-legend-precipitation",b[e]),n("precipitation-legend-thunder",_[e]),n("precipitation-legend-cloud-cover",x[e])}const k=(e,t,n)=>{const a=Chart.helpers.getRelativePosition(e,n),i=function(e){return 24===L||48===L?e&&e>0?e>=p.length-C?p.length-C-1:e:0:e&&e>0?e>=p.length?p.length-1:e:0}(n.scales.x.getValueForPixel(a.x));$(i)},M=(e,t)=>{const n=0===C?E:C;return 24===L?t%6==0?g[t+n]:null:48===L?t%8==0?g[t+n]:null:t%24==0?g[t+n]:null};const S=()=>window.innerWidth<768?24:window.innerWidth<1024?48:p.length;let L=S(),C=0,F=C+L;const H={size:24===L?14:18,weight:"semi-bold"},P=e=>({aspectRatio:1.75,onHover:k,borderRadius:5,maintainAspectRatio:!1,plugins:{legend:{display:!1},tooltip:{enabled:!1}},responsive:!0,interaction:{intersect:!1,axis:"x"},scales:{x:{ticks:{callback:M}},y:{grid:{color:"#1B1B1B"},beginAtZero:!0,max:e??10,ticks:{callback:function(e){return e%2!=0?"":e},font:H}}}}),D=new Chart(document.getElementById("forecast"),{type:"bar",plugins:[T],data:{labels:0===C?g.slice(E,F):g.slice(C,F),datasets:[{label:"wave height (feet)",data:0===C?p.slice(E,F):p.slice(C,F),pointStyle:!1,minBarLength:.1}]},options:{...P(void 0),elements:{bar:{backgroundColor:e=>(e=>0===C?u[e.dataIndex+E]:u[e.dataIndex+C])(e)||"#4ade80"}}}});function N(e){n("forecast-range",e)}function O(){if(0===C){let e=a>20&&(new Date).getHours()>20;N(24===L?e?"Tomorrow":"Today":e?`Tomorrow - ${g[C+25].split(" ")[0]}`:"Today - Tomorrow")}else if(24===L)N(g[C].split(" ")[0]);else{const e=g[F-1]??g[g.length-1];N(`${g[C].split(" ")[0]} - ${e.split(" ")[0]}`)}}function J(){const e=0===C?E:C;let t=g.slice(e,F);D.data.labels=t,D.data.datasets[0].data=p.slice(e,F),D.update(),U.data.labels=t,U.data.datasets[0].data=y.slice(e,F),U.update(),z.data.labels=t,z.data.datasets[0].data=b.slice(e,F),z.update()}O(),window.addEventListener("resize",(()=>{L=S(),C+L>g.length?(F=g.length,C=g.length-L):F=C+L,J(),$(0),0===C&&(t(document.getElementById("forecast-backward")).disabled=!0),C+L=p.length&&(t(document.getElementById("forecast-foreward")).disabled=!0),C>0&&(t(document.getElementById("forecast-backward")).disabled=!1),O()})),document.getElementById("forecast-backward")?.addEventListener("click",(()=>{C-L<0?(C=0,F=L):(F-=L,C-=L),J(),0===C?B(A):$(0),0===C&&(t(document.getElementById("forecast-backward")).disabled=!0),C+L{F+L>g.length?(F=g.length,C=F-L):(C+=L,F+=L),J(),$(0),C+L>=p.length&&(t(document.getElementById("forecast-foreward")).disabled=!0),C>0&&(t(document.getElementById("forecast-backward")).disabled=!1),O()}));const W=(e,t,n)=>({type:"bar",plugins:[T],data:{labels:0===C?g.slice(E,F):g.slice(C,F),datasets:[{label:n,data:e,pointStyle:!1,minBarLength:.1}]},options:{...P(100),elements:{bar:t?{backgroundColor:t,borderColor:t}:{}}}}),R=document.getElementById("temperature-forecast"),U=new Chart(R,W(0===C?y.slice(E,F):y.slice(C,F),"pink","F")),V=document.getElementById("precipitation-forecast"),z=new Chart(V,W(0===C?b.slice(E,F):b.slice(C,F),null,"%"));function G(e){const t=D.getElementsAtEventForMode(e,"nearest",{axis:"x",intersect:!1},!0);if(t[0]){const e=t[0].datasetIndex,n=t[0].index;U.tooltip.setActiveElements([{datasetIndex:e,index:n}]),U.setActiveElements([{datasetIndex:e,index:n}]),U.update(),z.tooltip.setActiveElements([{datasetIndex:e,index:n}]),z.setActiveElements([{datasetIndex:e,index:n}]),z.update()}else U.tooltip.setActiveElements([],{x:0,y:0}),U.setActiveElements([],{x:0,y:0}),U.update(),z.tooltip.setActiveElements([],{x:0,y:0}),z.setActiveElements([],{x:0,y:0}),z.update()}function j(e){const t=U.getElementsAtEventForMode(e,"nearest",{axis:"x",intersect:!1},!0);if(t[0]){const e=t[0].datasetIndex,n=t[0].index;D.tooltip.setActiveElements([{datasetIndex:e,index:n}]),D.setActiveElements([{datasetIndex:e,index:n}]),D.update(),z.tooltip.setActiveElements([{datasetIndex:e,index:n}]),z.setActiveElements([{datasetIndex:e,index:n}]),z.update()}else D.tooltip.setActiveElements([],{x:0,y:0}),D.setActiveElements([],{x:0,y:0}),D.update(),z.tooltip.setActiveElements([],{x:0,y:0}),z.setActiveElements([],{x:0,y:0}),z.update()}function Z(e){const t=z.getElementsAtEventForMode(e,"nearest",{axis:"x",intersect:!1},!0);if(t[0]){const e=t[0].datasetIndex,n=t[0].index;U.tooltip.setActiveElements([{datasetIndex:e,index:n}]),U.setActiveElements([{datasetIndex:e,index:n}]),U.update(),D.tooltip.setActiveElements([{datasetIndex:e,index:n}]),D.setActiveElements([{datasetIndex:e,index:n}]),D.update()}else U.tooltip.setActiveElements([],{x:0,y:0}),U.setActiveElements([],{x:0,y:0}),U.update(),D.tooltip.setActiveElements([],{x:0,y:0}),D.setActiveElements([],{x:0,y:0}),D.update()}D.canvas.onmousemove=G,U.canvas.onmousemove=j,z.canvas.onmousemove=Z,D.canvas.ontouchmove=G,U.canvas.ontouchmove=j,z.canvas.ontouchmove=Z}const A=e=>e.wind_speed===e.gusts||"0"===e.gusts?e.wind_speed:`${e.wind_speed}-${e.gusts}`;function I(e){o(".water-quality-loader"),s("current-water-quality"),n("current-water-quality-title","Closed for season"===e.water_quality?"-----":e.water_quality.toUpperCase()),n("current-water-quality-status-text",e.water_quality_text),"Advisory"===e.water_quality&&i("current-water-quality-title","color: #facc15;"),"Closed"===e.water_quality&&i("current-water-quality-title","color: #ef4444;")}function q(t){var n,a;l("forecast-container"),n="forecast-error",a=`\n
\n

\n Error loading forecast data - please refresh the page or try again later.\n

\n

${t}

\n
\n `,e(document.getElementById(n)).innerHTML=a}const T=e(document.querySelector("body"));new MutationObserver((t=>{for(const c of t)if(c.target instanceof HTMLElement&&("realtime-data"===c.target.id&&((a=JSON.parse(c.target.innerText)).wave_height&&(n("current-wave-height",a.wave_height),i("wave-icon",`transform: rotate(${a.wave_direction}deg);`),o(".wavey")),a.wave_period&&(n("current-wave-period",a.wave_period),l("wavey-period-loader")),(""===e(document.getElementById("wave-quality-text")).innerText||a.wave_height||parseFloat(e(document.getElementById("current-wave-height")).innerText??"0")>=1)&&(i("wave-quality",`background-color: ${a.quality_color};`),n("wave-quality-text",a.quality_text),i("wave-quality-text",`color: ${a.quality_color}`),o(".wave-quality-loader")),n("current-water-temp",a.water_temp),n("current-air-temp",a.air_temp),n("current-air-temp-2",a.air_temp),n("wind",A(a)),n("as-of",`As of ${a.as_of}`),i("wind-icon",`transform: rotate(${a.wind_direction+180}deg);`),o(".latest-loader"),s("wind-icon-container"),s("wave-icon-container"),r("as-of-container","animate-pulse"),s("wave-quality")),"water-quality-data"===c.target.id&&I(JSON.parse(c.target.innerText)),"forecast-data"===c.target.id))try{E(JSON.parse(c.target.innerText))}catch{setTimeout((()=>{if(c.target instanceof HTMLElement)try{E(JSON.parse(c.target.innerText))}catch(e){setTimeout((()=>{if(c.target instanceof HTMLElement)try{E(JSON.parse(c.target.innerText))}catch(e){q(e)}}),1e3)}}),400)}var a})).observe(T,{attributes:!0,childList:!0,subtree:!0})}(); +!function(){"use strict";function e(e){if(null==e)throw"item is null or undefined";return e}function t(e){if(null==e)throw"item is null or undefined";if(e instanceof HTMLButtonElement)return e;throw"item is not a button"}function n(t,n){e(document.getElementById(t)).innerText=n}function a(e,t,n){document.getElementById(e)?.setAttribute(t,n)}function i(e,t){e instanceof Array?e.forEach((e=>{a(e,"style",t)})):a(e,"style",t)}function o(e){document.querySelectorAll(e).forEach((e=>e.remove()))}function l(e){document.getElementById(e)?.remove()}function r(e,t){document.getElementById(e)?.classList.remove(t)}function s(e){r(e,"hidden")}const c="#a8a29e",d={"#0bd674":"Good","#ffcd1e":"Fair to Good","#ff9500":"Poor","#f4496d":"Very Poor","#a8a29e":"Flat"};let u,g,p,m,f,w,v,y,h,x,b,_;function E(e){const a=new Date(e.starting_at).getHours();let E=new Date(e.starting_at).getHours();if(a>20){let t=24-a;const n=e.wave_height_labels.length-(e.wave_height_labels.length-t)%24;E=0,g=e.wave_height_labels.slice(t,n),u=e.quality.slice(t,n),p=e.wave_height.slice(t,n),m=e.wind_speed.slice(t,n),f=e.wind_direction.slice(t,n),w=e.wind_gust.slice(t,n),v=e.wave_period.slice(t,n),y=e.temperature.slice(t,n),h=e.dewpoint.slice(t,n),x=e.cloud_cover.slice(t,n),b=e.probability_of_precipitation.slice(t,n),_=e.probability_of_thunder.slice(t,n)}else{const t=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][new Date(e.starting_at).getDay()],n=[];for(let e=0;e=10&&e<12&&n.push(`${t} ${e} AM`),12===e&&n.push(`${t} ${e} PM`),e>12&&n.push(`${t} 0${e-12} PM`)):n.push(`${t} 12 AM`);const i=e.wave_height_labels.length+a-(e.wave_height_labels.length+a)%24;g=n.concat(e.wave_height_labels).slice(0,i),u=new Array(a).fill("#a8a29e").concat(e.quality).slice(0,i),p=new Array(a).fill(0).concat(e.wave_height).slice(0,i),m=new Array(a).fill(0).concat(e.wind_speed).slice(0,i),f=new Array(a).fill(0).concat(e.wind_direction).slice(0,i),w=new Array(a).fill(0).concat(e.wind_gust).slice(0,i),v=new Array(a).fill(0).concat(e.wave_period).slice(0,i),y=new Array(a).fill(0).concat(e.temperature).slice(0,i),h=new Array(a).fill(0).concat(e.dewpoint).slice(0,i),x=new Array(a).fill(0).concat(e.cloud_cover).slice(0,i),b=new Array(a).fill(0).concat(e.probability_of_precipitation).slice(0,i),_=new Array(a).fill(0).concat(e.probability_of_thunder).slice(0,i)}let A=(new Date).getHours();const I=document.getElementById("current-wave-height");""===I?.innerText&&(I.innerText=e.current_wave_height,"0"==e.current_wave_height&&(i("wave-quality",`background-color: ${c}`),n("wave-quality-text",d[c]),i("wave-quality-text",`color: ${c}`),o(".wave-quality-loader")),i("wave-icon",`transform: rotate(${e.current_wave_direction}deg);`),o(".wavey"));const q=document.getElementById("current-wave-period");""===q?.innerText&&(q.innerText=e.current_wave_period,l("wavey-period-loader")),n("legend-label",g[A]),n("legend-quality",d[u[A]]),n("legend-wave-height",p[A]),n("legend-wind-speed",m[A]),i("legend-wind-icon",`transform: rotate(${f[A]+180}deg);`),n("legend-wave-period",v[A]),n("legend-wind-gust",w[A]),n("forecast-as-of",`Updated ${e.as_of}`),n("forecast-as-of-2",`Updated ${e.as_of}`),o(".loader"),s("forecast"),s("wave-quality"),s("legend-container"),r("forecast-as-of-container","animate-pulse"),r("forecast-as-of-container-2","animate-pulse"),s("temperature-legend-container"),n("temperature-legend-label",g[A]),n("temperature-legend-temperature",y[A]),n("temperature-legend-dewpoint",h[A]),s("precipitation-legend-container"),n("precipitation-legend-label",g[A]),n("precipitation-legend-precipitation",b[A]),n("precipitation-legend-thunder",_[A]),n("precipitation-legend-cloud-cover",x[A]),i("legend",`background-color: ${u[A]};`),i(["legend-container","precipitation-legend-container","temperature-legend-container"],"margin-top: 1rem;");const T={id:"vert",defaults:{width:1,dash:[3,3]},afterInit:(e,t,n)=>{e.corsair={x:0,y:0}},afterDraw:(e,t,n)=>{if(e.tooltip?._active?.length){let t=e.tooltip._active[0].element.x,a=e.scales.y,i=e.ctx;i.save(),i.beginPath(),i.moveTo(t,a.top),i.lineTo(t,a.bottom),i.lineWidth=1,i.setLineDash(n.dash),i.strokeStyle="#fff",i.stroke(),i.restore()}if(A-E>C){const t=e.ctx,n=e.scales.x.getPixelForValue(A-E);t.save(),t.strokeStyle="#5b5b58",t.lineWidth=1,t.beginPath(),t.moveTo(n,0),t.lineTo(n,e.height),t.stroke(),t.fillStyle="#5b5b58",t.font="bold 1rem ui-sans-serif, system-ui, sans-serif",t.fillText("Now",A>14&&24===L?n-45:n+5,15),t.restore()}}};function $(e){let t;t=e>=g.length-a?g.length-a-1:e;B(t+(0===C?E:C))}function B(e){const t=u[e];i("legend",`background-color: ${t}`),n("legend-label",g[e]),n("legend-quality",d[t]),n("legend-wave-height",p[e]),n("legend-wind-speed",m[e]),i("legend-wind-icon",`transform: rotate(${f[e]+180}deg);`),n("legend-wave-period",v[e]),n("legend-wind-gust",w[e]),n("temperature-legend-label",g[e]),n("temperature-legend-temperature",y[e]),n("temperature-legend-dewpoint",h[e]),n("precipitation-legend-label",g[e]),n("precipitation-legend-precipitation",b[e]),n("precipitation-legend-thunder",_[e]),n("precipitation-legend-cloud-cover",x[e])}const k=(e,t,n)=>{const a=Chart.helpers.getRelativePosition(e,n),i=function(e){return 24===L||48===L?e&&e>0?e>=p.length-C?p.length-C-1:e:0:e&&e>0?e>=p.length?p.length-1:e:0}(n.scales.x.getValueForPixel(a.x));$(i)},M=(e,t)=>{const n=0===C?E:C;return 24===L?t%6==0?g[t+n]:null:48===L?t%8==0?g[t+n]:null:t%24==0?g[t+n]:null};const S=()=>window.innerWidth<768?24:window.innerWidth<1024?48:p.length;let L=S(),C=0,F=C+L;const H={size:24===L?14:18,weight:"semi-bold"},P=e=>({aspectRatio:1.75,onHover:k,borderRadius:5,maintainAspectRatio:!1,plugins:{legend:{display:!1},tooltip:{enabled:!1}},responsive:!0,interaction:{intersect:!1,axis:"x"},scales:{x:{ticks:{callback:M}},y:{grid:{color:"#1B1B1B"},beginAtZero:!0,max:e??10,ticks:{callback:function(e){return e%2!=0?"":e},font:H}}}}),D=new Chart(document.getElementById("forecast"),{type:"bar",plugins:[T],data:{labels:0===C?g.slice(E,F):g.slice(C,F),datasets:[{label:"wave height (feet)",data:0===C?p.slice(E,F):p.slice(C,F),pointStyle:!1,minBarLength:.1}]},options:{...P(void 0),elements:{bar:{backgroundColor:e=>(e=>0===C?u[e.dataIndex+E]:u[e.dataIndex+C])(e)||"#4ade80"}}}});function N(e){n("forecast-range",e)}function O(){if(0===C){let e=a>20&&(new Date).getHours()>20;N(24===L?e?"Tomorrow":"Today":e?`Tomorrow - ${g[C+25].split(" ")[0]}`:"Today - Tomorrow")}else if(24===L)N(g[C].split(" ")[0]);else{const e=g[F-1]??g[g.length-1];N(`${g[C].split(" ")[0]} - ${e.split(" ")[0]}`)}}function J(){const e=0===C?E:C;let t=g.slice(e,F);D.data.labels=t,D.data.datasets[0].data=p.slice(e,F),D.update(),U.data.labels=t,U.data.datasets[0].data=y.slice(e,F),U.update(),z.data.labels=t,z.data.datasets[0].data=b.slice(e,F),z.update()}O(),window.addEventListener("resize",(()=>{L=S(),C+L>g.length?(F=g.length,C=g.length-L):F=C+L,J(),$(0),0===C&&(t(document.getElementById("forecast-backward")).disabled=!0),C+L=p.length&&(t(document.getElementById("forecast-foreward")).disabled=!0),C>0&&(t(document.getElementById("forecast-backward")).disabled=!1),O()})),document.getElementById("forecast-backward")?.addEventListener("click",(()=>{C-L<0?(C=0,F=L):(F-=L,C-=L),J(),0===C?B(A):$(0),0===C&&(t(document.getElementById("forecast-backward")).disabled=!0),C+L{F+L>g.length?(F=g.length,C=F-L):(C+=L,F+=L),J(),$(0),C+L>=p.length&&(t(document.getElementById("forecast-foreward")).disabled=!0),C>0&&(t(document.getElementById("forecast-backward")).disabled=!1),O()}));const W=(e,t,n)=>({type:"bar",plugins:[T],data:{labels:0===C?g.slice(E,F):g.slice(C,F),datasets:[{label:n,data:e,pointStyle:!1,minBarLength:.1}]},options:{...P(100),elements:{bar:t?{backgroundColor:t,borderColor:t}:{}}}}),R=document.getElementById("temperature-forecast"),U=new Chart(R,W(0===C?y.slice(E,F):y.slice(C,F),"pink","F")),V=document.getElementById("precipitation-forecast"),z=new Chart(V,W(0===C?b.slice(E,F):b.slice(C,F),null,"%"));function G(e){const t=D.getElementsAtEventForMode(e,"nearest",{axis:"x",intersect:!1},!0);if(t[0]){const e=t[0].datasetIndex,n=t[0].index;U.tooltip.setActiveElements([{datasetIndex:e,index:n}]),U.setActiveElements([{datasetIndex:e,index:n}]),U.update(),z.tooltip.setActiveElements([{datasetIndex:e,index:n}]),z.setActiveElements([{datasetIndex:e,index:n}]),z.update()}else U.tooltip.setActiveElements([],{x:0,y:0}),U.setActiveElements([],{x:0,y:0}),U.update(),z.tooltip.setActiveElements([],{x:0,y:0}),z.setActiveElements([],{x:0,y:0}),z.update()}function j(e){const t=U.getElementsAtEventForMode(e,"nearest",{axis:"x",intersect:!1},!0);if(t[0]){const e=t[0].datasetIndex,n=t[0].index;D.tooltip.setActiveElements([{datasetIndex:e,index:n}]),D.setActiveElements([{datasetIndex:e,index:n}]),D.update(),z.tooltip.setActiveElements([{datasetIndex:e,index:n}]),z.setActiveElements([{datasetIndex:e,index:n}]),z.update()}else D.tooltip.setActiveElements([],{x:0,y:0}),D.setActiveElements([],{x:0,y:0}),D.update(),z.tooltip.setActiveElements([],{x:0,y:0}),z.setActiveElements([],{x:0,y:0}),z.update()}function Z(e){const t=z.getElementsAtEventForMode(e,"nearest",{axis:"x",intersect:!1},!0);if(t[0]){const e=t[0].datasetIndex,n=t[0].index;U.tooltip.setActiveElements([{datasetIndex:e,index:n}]),U.setActiveElements([{datasetIndex:e,index:n}]),U.update(),D.tooltip.setActiveElements([{datasetIndex:e,index:n}]),D.setActiveElements([{datasetIndex:e,index:n}]),D.update()}else U.tooltip.setActiveElements([],{x:0,y:0}),U.setActiveElements([],{x:0,y:0}),U.update(),D.tooltip.setActiveElements([],{x:0,y:0}),D.setActiveElements([],{x:0,y:0}),D.update()}D.canvas.onmousemove=G,U.canvas.onmousemove=j,z.canvas.onmousemove=Z,D.canvas.ontouchmove=G,U.canvas.ontouchmove=j,z.canvas.ontouchmove=Z}const A=e=>e.wind_speed===e.gusts||"0"===e.gusts?e.wind_speed:`${e.wind_speed}-${e.gusts}`;function I(e){o(".water-quality-loader"),s("current-water-quality"),n("current-water-quality-title","Closed for season"===e.water_quality?"-----":e.water_quality.toUpperCase()),n("current-water-quality-status-text",e.water_quality_text),"Advisory"===e.water_quality&&i("current-water-quality-title","color: #facc15;"),"Closed"===e.water_quality&&i("current-water-quality-title","color: #ef4444;")}function q(t){var n,a;l("forecast-container"),n="forecast-error",a=`\n
\n

\n Error loading forecast data - please refresh the page or try again later.\n

\n

${t}

\n
\n `,e(document.getElementById(n)).innerHTML=a}const T=e(document.querySelector("body"));new MutationObserver((t=>{for(const c of t)if(c.target instanceof HTMLElement&&("realtime-data"===c.target.id&&((a=JSON.parse(c.target.innerText)).wave_height&&(n("current-wave-height",a.wave_height),i("wave-icon",`transform: rotate(${a.wave_direction}deg);`),o(".wavey")),a.wave_period&&(n("current-wave-period",a.wave_period),l("wavey-period-loader")),(""===e(document.getElementById("wave-quality-text")).innerText||a.wave_height||parseFloat(e(document.getElementById("current-wave-height")).innerText??"0")>=1)&&(i("wave-quality",`background-color: ${a.quality_color};`),n("wave-quality-text",a.quality_text),i("wave-quality-text",`color: ${a.quality_color}`),o(".wave-quality-loader")),n("current-water-temp",a.water_temp),n("current-air-temp",a.air_temp),n("current-air-temp-2",a.air_temp),n("wind",A(a)),n("as-of",`As of ${a.as_of}`),i("wind-icon",`transform: rotate(${a.wind_direction+180}deg);`),o(".latest-loader"),s("wind-icon-container"),s("wave-icon-container"),r("as-of-container","animate-pulse"),s("wave-quality"),a.loaded_from_fallback&&s("wave-fallback-icon")),"water-quality-data"===c.target.id&&I(JSON.parse(c.target.innerText)),"forecast-data"===c.target.id))try{E(JSON.parse(c.target.innerText))}catch{setTimeout((()=>{if(c.target instanceof HTMLElement)try{E(JSON.parse(c.target.innerText))}catch(e){setTimeout((()=>{if(c.target instanceof HTMLElement)try{E(JSON.parse(c.target.innerText))}catch(e){q(e)}}),1e3)}}),400)}var a})).observe(T,{attributes:!0,childList:!0,subtree:!0})}(); diff --git a/assets/styles.css b/assets/styles.css index cb0aa29..4ac4725 100644 --- a/assets/styles.css +++ b/assets/styles.css @@ -1,2 +1,2 @@ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}html{scroll-behavior:smooth}body{font-family:Satoshi,sans-serif}h1{font-size:2rem;font-weight:bolder;margin-bottom:2rem}@media screen and (min-width:640px){h1{font-size:3rem}}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border-width:0;white-space:nowrap}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.left-0{left:0}.left-full{left:100%}.right-0{right:0}.top-0{top:0}.top-24{top:6rem}.top-\[64px\]{top:64px}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.order-first{order:-9999}.-m-2\.5{margin:-.625rem}.m-auto{margin:auto}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-8{margin-bottom:2rem;margin-top:2rem}.mb-0{margin-bottom:0}.mb-10{margin-bottom:2.5rem}.mb-20{margin-bottom:5rem}.ml-2{margin-left:.5rem}.mr-16{margin-right:4rem}.mr-2{margin-right:.5rem}.mr-\[10px\]{margin-right:10px}.mt-14{margin-top:3.5rem}.mt-2{margin-top:.5rem}.mt-28{margin-top:7rem}.mt-4{margin-top:1rem}.mt-auto{margin-top:auto}.block{display:block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[118px\]{height:118px}.h-\[150px\]{height:150px}.h-\[20px\]{height:20px}.h-\[315px\]{height:315px}.h-\[40px\]{height:40px}.h-full{height:100%}.max-h-96{max-height:24rem}.max-h-\[24px\]{max-height:24px}.w-16{width:4rem}.w-2{width:.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-80{width:20rem}.w-\[10px\]{width:10px}.w-\[40px\]{width:40px}.w-\[6px\]{width:6px}.w-\[90\%\]{width:90%}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.min-w-10{min-width:2.5rem}.min-w-72{min-width:18rem}.min-w-8{min-width:2rem}.max-w-7xl{max-width:80rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-px{gap:1px}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.gap-y-2{row-gap:.5rem}.gap-y-5{row-gap:1.25rem}.gap-y-7{row-gap:1.75rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.self-center{align-self:center}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-white{--tw-border-opacity:1;border-color:rgb(255 255 255/var(--tw-border-opacity))}.border-white\/15{border-color:hsla(0,0%,100%,.15)}.border-white\/5{border-color:hsla(0,0%,100%,.05)}.bg-\[\#36a2eb\]{--tw-bg-opacity:1;background-color:rgb(54 162 235/var(--tw-bg-opacity))}.bg-\[\#ffc0cb\]{--tw-bg-opacity:1;background-color:rgb(255 192 203/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-black\/10{background-color:rgba(0,0,0,.1)}.bg-black\/80{background-color:rgba(0,0,0,.8)}.bg-black\/90{background-color:rgba(0,0,0,.9)}.bg-gray-400\/10{background-color:rgba(156,163,175,.1)}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.bg-gray-700\/10{background-color:rgba(55,65,81,.1)}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity))}.fill-black{fill:#000}.fill-gray-300{fill:#d1d5db}.fill-white{fill:#fff}.fill-yellow-500{fill:#eab308}.p-12{padding:3rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.pt-5{padding-top:1.25rem}.pt-\[3px\]{padding-top:3px}.text-center{text-align:center}.text-end{text-align:end}.align-middle{vertical-align:middle}.font-\[\'Array\'\]{font-family:Array}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-light{font-weight:300}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-3{line-height:.75rem}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.tracking-tight{letter-spacing:-.025em}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-inset{--tw-ring-inset:inset}.ring-gray-400\/30{--tw-ring-color:rgba(156,163,175,.3)}.ring-white\/10{--tw-ring-color:hsla(0,0%,100%,.1)}.backdrop-blur-md{--tw-backdrop-blur:blur(12px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.hover\:bg-gray-800:hover{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.hover\:fill-gray-300:hover{fill:#d1d5db}.hover\:fill-gray-500:hover{fill:#6b7280}.hover\:text-gray-300:hover{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.disabled\:border-zinc-700:disabled{--tw-border-opacity:1;border-color:rgb(63 63 70/var(--tw-border-opacity))}.disabled\:text-zinc-700:disabled{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity))}.group:hover .group-hover\:text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}@media (min-width:640px){.sm\:order-none{order:0}.sm\:ml-6{margin-left:1.5rem}.sm\:mt-16{margin-top:4rem}.sm\:block{display:block}.sm\:hidden{display:none}.sm\:h-8{height:2rem}.sm\:w-8{width:2rem}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:self-center{align-self:center}.sm\:border-l{border-left-width:1px}.sm\:p-3{padding:.75rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-2{padding-bottom:.5rem;padding-top:.5rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}}@media (min-width:768px){.md\:mt-10{margin-top:2.5rem}.md\:grid{display:grid}.md\:h-\[472px\]{height:472px}.md\:min-w-32{min-width:8rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:space-y-16>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(4rem*var(--tw-space-y-reverse));margin-top:calc(4rem*(1 - var(--tw-space-y-reverse)))}}@media (min-width:1024px){.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:hidden{display:none}.lg\:h-\[630px\]{height:630px}.lg\:max-h-\[600px\]{max-height:600px}.lg\:w-\[1040px\]{width:1040px}.lg\:w-full{width:100%}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-between{justify-content:space-between}.lg\:border-l{border-left-width:1px}.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width:1280px){.xl\:w-full{width:100%}.xl\:text-center{text-align:center}} \ No newline at end of file +/*! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}html{scroll-behavior:smooth}body{font-family:Satoshi,sans-serif}h1{font-size:2rem;font-weight:bolder;margin-bottom:2rem}@media screen and (min-width:640px){h1{font-size:3rem}}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border-width:0;white-space:nowrap}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.left-0{left:0}.left-full{left:100%}.right-0{right:0}.top-0{top:0}.top-24{top:6rem}.top-\[64px\]{top:64px}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.order-first{order:-9999}.-m-2\.5{margin:-.625rem}.m-auto{margin:auto}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-8{margin-bottom:2rem;margin-top:2rem}.mb-0{margin-bottom:0}.mb-10{margin-bottom:2.5rem}.mb-20{margin-bottom:5rem}.ml-2{margin-left:.5rem}.mr-16{margin-right:4rem}.mr-2{margin-right:.5rem}.mr-\[10px\]{margin-right:10px}.mt-14{margin-top:3.5rem}.mt-2{margin-top:.5rem}.mt-28{margin-top:7rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-auto{margin-top:auto}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-16{height:4rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[118px\]{height:118px}.h-\[150px\]{height:150px}.h-\[20px\]{height:20px}.h-\[315px\]{height:315px}.h-\[40px\]{height:40px}.h-full{height:100%}.max-h-96{max-height:24rem}.max-h-\[24px\]{max-height:24px}.min-h-full{min-height:100%}.w-12{width:3rem}.w-16{width:4rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-80{width:20rem}.w-\[10px\]{width:10px}.w-\[40px\]{width:40px}.w-\[6px\]{width:6px}.w-\[90\%\]{width:90%}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-screen{width:100vw}.min-w-10{min-width:2.5rem}.min-w-72{min-width:18rem}.min-w-8{min-width:2rem}.max-w-7xl{max-width:80rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow{flex-grow:1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-px{gap:1px}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.gap-y-2{row-gap:.5rem}.gap-y-5{row-gap:1.25rem}.gap-y-7{row-gap:1.75rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.self-center{align-self:center}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-white{--tw-border-opacity:1;border-color:rgb(255 255 255/var(--tw-border-opacity))}.border-white\/15{border-color:hsla(0,0%,100%,.15)}.border-white\/5{border-color:hsla(0,0%,100%,.05)}.bg-\[\#36a2eb\]{--tw-bg-opacity:1;background-color:rgb(54 162 235/var(--tw-bg-opacity))}.bg-\[\#ffc0cb\]{--tw-bg-opacity:1;background-color:rgb(255 192 203/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-black\/10{background-color:rgba(0,0,0,.1)}.bg-black\/80{background-color:rgba(0,0,0,.8)}.bg-black\/90{background-color:rgba(0,0,0,.9)}.bg-gray-400\/10{background-color:rgba(156,163,175,.1)}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.bg-gray-700\/10{background-color:rgba(55,65,81,.1)}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity))}.fill-black{fill:#000}.fill-gray-300{fill:#d1d5db}.fill-white{fill:#fff}.fill-yellow-400{fill:#facc15}.p-12{padding:3rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-4{padding-top:1rem}.pb-4,.py-4{padding-bottom:1rem}.pt-5{padding-top:1.25rem}.pt-\[3px\]{padding-top:3px}.text-left{text-align:left}.text-center{text-align:center}.text-end{text-align:end}.align-middle{vertical-align:middle}.font-\[\'Array\'\]{font-family:Array}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-light{font-weight:300}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-3{line-height:.75rem}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.tracking-tight{letter-spacing:-.025em}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-sm,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-inset{--tw-ring-inset:inset}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.ring-gray-400\/30{--tw-ring-color:rgba(156,163,175,.3)}.ring-white\/10{--tw-ring-color:hsla(0,0%,100%,.1)}.backdrop-blur-md{--tw-backdrop-blur:blur(12px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.hover\:bg-gray-800:hover{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.hover\:fill-gray-300:hover{fill:#d1d5db}.hover\:fill-gray-500:hover{fill:#6b7280}.hover\:fill-yellow-600:hover{fill:#ca8a04}.hover\:text-gray-300:hover{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.disabled\:border-zinc-700:disabled{--tw-border-opacity:1;border-color:rgb(63 63 70/var(--tw-border-opacity))}.disabled\:text-zinc-700:disabled{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity))}.group:hover .group-hover\:text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}@media (min-width:640px){.sm\:order-none{order:0}.sm\:mx-0{margin-left:0;margin-right:0}.sm\:my-8{margin-bottom:2rem;margin-top:2rem}.sm\:ml-4{margin-left:1rem}.sm\:ml-6{margin-left:1.5rem}.sm\:mt-0{margin-top:0}.sm\:mt-16{margin-top:4rem}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:h-10{height:2.5rem}.sm\:h-8{height:2rem}.sm\:w-10{width:2.5rem}.sm\:w-8{width:2rem}.sm\:w-auto{width:auto}.sm\:w-full{width:100%}.sm\:max-w-lg{max-width:32rem}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:flex-row-reverse{flex-direction:row-reverse}.sm\:items-start{align-items:flex-start}.sm\:items-center{align-items:center}.sm\:self-center{align-self:center}.sm\:border-l{border-left-width:1px}.sm\:p-0{padding:0}.sm\:p-3{padding:.75rem}.sm\:p-6{padding:1.5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-2{padding-bottom:.5rem;padding-top:.5rem}.sm\:pb-4{padding-bottom:1rem}.sm\:text-left{text-align:left}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}}@media (min-width:768px){.md\:mt-10{margin-top:2.5rem}.md\:grid{display:grid}.md\:h-\[472px\]{height:472px}.md\:min-w-32{min-width:8rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:space-y-16>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(4rem*var(--tw-space-y-reverse));margin-top:calc(4rem*(1 - var(--tw-space-y-reverse)))}}@media (min-width:1024px){.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:hidden{display:none}.lg\:h-\[630px\]{height:630px}.lg\:max-h-\[600px\]{max-height:600px}.lg\:w-\[1040px\]{width:1040px}.lg\:w-full{width:100%}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-between{justify-content:space-between}.lg\:border-l{border-left-width:1px}.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width:1280px){.xl\:w-full{width:100%}.xl\:text-center{text-align:center}} \ No newline at end of file diff --git a/client/parsers/realtime.js b/client/parsers/realtime.js index 7555ff7..5b2da2a 100644 --- a/client/parsers/realtime.js +++ b/client/parsers/realtime.js @@ -21,6 +21,7 @@ import { * @property {?string} wave_direction * @property {?string} wave_period * @property {string} as_of + * @property {boolean} loaded_from_fallback - Wether the latest data used a bouy or land data. */ /** @@ -83,6 +84,10 @@ export function parseRealtime(data) { removeHidden("wave-icon-container"); removeStyle("as-of-container", "animate-pulse"); removeHidden("wave-quality"); + + if (data.loaded_from_fallback) { + removeHidden("wave-fallback-icon"); + } } /** diff --git a/src/realtime.rs b/src/realtime.rs index 211dc81..b44e3c5 100644 --- a/src/realtime.rs +++ b/src/realtime.rs @@ -23,6 +23,7 @@ pub struct Realtime { pub wave_height: Option, pub wave_period: Option, pub wave_direction: Option, + pub loaded_from_fallback: bool, } impl Realtime { @@ -31,6 +32,8 @@ impl Realtime { let data = Self::get_latest_data(&spot, realtime_url).await?; + let mut loaded_from_fallback = !spot.has_bouy; + let latest = data.lines().collect::>(); let line = latest.get(2).unwrap(); @@ -41,15 +44,32 @@ impl Realtime { // Check if the bouy data is older than a day, if so fallback to other path. if Utc::now() - as_of > TimeDelta::days(1) { let data = Self::get_fallback_data(&spot, realtime_url, FALLBACK_BOUY).await?; + loaded_from_fallback = true; let latest = data.lines().collect::>(); let line = latest.get(2).unwrap(); let (as_of, measurements) = line.split_at(16); let as_of = Self::parse_as_of(as_of)?; - return Self::parse_data(measurements, &latest, &as_of, &spot, realtime_url).await; + return Self::parse_data( + measurements, + &latest, + &as_of, + &spot, + realtime_url, + loaded_from_fallback, + ) + .await; } - Self::parse_data(measurements, &latest, &as_of, &spot, realtime_url).await + Self::parse_data( + measurements, + &latest, + &as_of, + &spot, + realtime_url, + loaded_from_fallback, + ) + .await } async fn parse_data( @@ -58,6 +78,7 @@ impl Realtime { as_of: &DateTime, spot: &Spot, realtime_url: &str, + loaded_from_fallback: bool, ) -> anyhow::Result { // MID Lake bouy is in the water yeat round // const MID_LAKE_BOUY: &str = "https://www.ndbc.noaa.gov/data/realtime2/45214.txt"; @@ -149,6 +170,7 @@ impl Realtime { wave_height, wave_period, wave_direction, + loaded_from_fallback, }) } diff --git a/src/spot.rs b/src/spot.rs index f532d9a..693d2bc 100644 --- a/src/spot.rs +++ b/src/spot.rs @@ -23,6 +23,7 @@ pub struct Spot { pub location: Location, pub live_feed_url: Option<&'static str>, pub name: &'static str, + pub has_bouy: bool, } fn get_status_query(id: &str) -> String { @@ -62,7 +63,8 @@ impl From for Spot { fallback_realtime_path: None, location: Location::Bradford, live_feed_url: None, - name: "Bradford" + name: "Bradford", + has_bouy: false, }, Location::PortWashington => Spot { forecast_path: PORT_WASHINGTON_PATH, @@ -72,7 +74,8 @@ impl From for Spot { fallback_realtime_path: None, location: Location::PortWashington, live_feed_url: None, - name: "Port Washington" + name: "Port Washington", + has_bouy: false, }, Location::Sheboygan => Spot { forecast_path: SHEBOYGAN_PATH, @@ -82,7 +85,8 @@ impl From for Spot { fallback_realtime_path: Some(SHEBOYGAN_FALLBACK_REALTIME_PATH), location: Location::Sheboygan, live_feed_url: Some("https://www.youtube-nocookie.com/embed/p780CkCgNVE?si=qBa_a4twCnOprcG1&controls=0"), - name: "Sheboygan - North" + name: "Sheboygan - North", + has_bouy: true, }, Location::SheboyganSouth => Spot { forecast_path: SHEBOYGAN_SOUTH_PATH, @@ -92,7 +96,8 @@ impl From for Spot { fallback_realtime_path: Some(SHEBOYGAN_FALLBACK_REALTIME_PATH), location: Location::SheboyganSouth, live_feed_url: Some("https://www.youtube.com/embed/M0Ion4MpsgU?si=yCi2OVy3RIbY_5kC&controls=0"), - name: "Sheboygan - South" + name: "Sheboygan - South", + has_bouy: true, }, Location::Racine => Spot { forecast_path: RACINE_PATH, @@ -102,7 +107,8 @@ impl From for Spot { fallback_realtime_path: Some(RACINE_FALLBACK_REALTIME_PATH), location: Location::Racine, live_feed_url: None, - name: "Racine" + name: "Racine", + has_bouy: true, }, Location::Atwater => Spot { forecast_path: ATWATER_PATH, @@ -113,6 +119,7 @@ impl From for Spot { location: Location::Atwater, live_feed_url: None, name: "Atwater", + has_bouy: true, }, } } diff --git a/templates/base.html b/templates/base.html index 02e509c..c8e0961 100644 --- a/templates/base.html +++ b/templates/base.html @@ -37,7 +37,7 @@ rel="stylesheet" /> - + @@ -66,7 +66,7 @@ @keyup.escape="showLiveFeed = false; showNav = false;" :class="{ 'overflow-hidden': showNav || showLiveFeed }" > - + {% block body %} {% endblock %} {% include "includes/footer.html" %} {% if live_reload %}