From a62062c44a7181d0d23d6ff80834bef014b11b32 Mon Sep 17 00:00:00 2001 From: vividfog <75913791+vividfog@users.noreply.github.com> Date: Mon, 4 Mar 2024 20:34:56 +0200 Subject: [PATCH] simplification --- data/prediction.db | Bin 1953792 -> 1953792 bytes deploy/index.html | 588 +--------------------------------- deploy/index_dev.html | 720 ++++++++++++++++++++++++++++++++++++++++++ deploy/scripts.js | 385 ++++++++++++++++++++++ deploy/styles.css | 204 ++++++++++++ 5 files changed, 1317 insertions(+), 580 deletions(-) create mode 100644 deploy/index_dev.html create mode 100644 deploy/scripts.js create mode 100644 deploy/styles.css diff --git a/data/prediction.db b/data/prediction.db index b50884a498ea876e28983a6ddd80ac07435fc64b..6b08404fe1fdff798402cbabff2072a114d87405 100644 GIT binary patch delta 8289 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 delta 5363 zcmeHLhgTHW8sFJn*v1wh)EOzVyR_Y9DHfWH4N*Xv8j65Q01IVf8&G61JT$s0HN5#N~Yc3wb;N6XtL8~f*2y8-ZB5Y!8PS~8WN$izDQm7Lx zCOYksR_SwuSp6aK4weR(F^-hO$~(U9@VbJ02IqyO!B!CDwo1_^#U{fh$ELvMf=!7{ zg-wl3gUuD2gX@m%Icy%-Jh6FU^Ty_5SG4&)nJ&R8FC-obn*GR=kU*1i7vXuuH;RuG zyA>-GWwvh`V#O{=eyfgCX@5Ue|Dq8 zh!IID=?2{c|RCTU#&av3upwI*)C|ZN4d1@oW7)SI?7qED zQm9<%E`$1AUJ{6pA;Y};O9)vq_V?*c7is-1`}_YcohFHe7hRn);d~5VNvIk(qWvIk zC{UNQAKMHW!^ju7u)24eZ{Iw$h7J|q$gj9M(~7jPJP9ZH-7qqf>qNlKwklJ{Nm{>O zWa+zp3{r=ag#%bcMB~y)L9~8P``U@u#=?o=WT`LL-AzW-G_emI9$GTv#`Xl+Q$Y@1 z+!?TL1fQE}23~{$0Uh~c`Hb%`#)E1kS<2;Rm~^Kma>nfO-+>}_P^#A#pMEzsmk#^k(gCOng!)*r zlCV&94M*$v#1wZtOaJp*GZtqF7?* zX%7|no7|)jn?S~L>@bfvb88bX(-9txyM7$YSWzU)-sZ3NgbPb3DcJi|YPgs{4&_`z z{Oj&E&7M!2HboBU>20+lGwTwZ$bUM)7rydb6)vR1zv^n97MBO>63J@jZwL;y9@P6WJOf}0t{8LIhqf`*Eu93msbfE6LA9K zpG2q7UDzx7SUgmG-06tZ7Ux$WvsF5>*-ZM0RT||3*g=z8t$4IpB>GmgSF}nrTQpG= zCQ=B`3*QoM5L$#;!YJYq0Yr_>+~5l|O*-2GEMhkzkBDQZr(%L4L?M&k!3#cEjN|$@ zjV2vaT?rlYs8KMa3jdU8NexU%#{;sUit-0tI_VEfs;F*oc3j0&5|D z979+SL>f5??mzIVW7EmMOKITNTI0?_bIw>tRJIxf+hv;M?E{e8 zTNq?@@O_|YDwR2ascYZ$7;%F^^(=O~f>J?y2L9;oO{L-nSQ>@fKkoTk&@#57%xQc@ z`cJ!@HXco!gX#b^<7#!4xW$HCN(NV_QQ-p$en-}X2q!RTDH|ub630n;o*KgyYFPg3 z)xv2*VZ-x$;>MMxnu^V83#tXp9V|`d@?j&5>$ZJ6Y%haqSV4QOMh&gG)M!r7OxZRs zZ_0(fTwc(0_~i(XQ?FalVz!RDJYLYG&GH)2sb5HvidgV@RMvNAz&xFgZ9bSLJR3NlLDj6& zdk&|KKWb!fZ8{Y@AYIu#Z~k#7234`F%MMn=PNfVg#okgVF60Z06s)g)=vxXcg?tc$ zWch|CA)ySaVBNP_aessxwG*agXXdPw~P2HwXc@$ zdbGERL33Fww0IEaXPEx-f-4M~!(we1;~F3I+RgSSJ7o6J$a|L1o{7A3{_^(^B21}X zFL0G~2qYb9U)39`7-hGTcKL@(zTyW(rTn41LGCQG$$X?crH20&wFS(yLI*<)?BMz~ z!=5EI6?B+%bVJ4mOD$*u`>yd+8Th~>IY2^Gc3MXJZ8lQ85GZA!Sz)3bNzkQJLm8DW2n`F;&3D2 zeh&2<>`%d)m&!{ng}>KR(Ht@O^oF1J1)a5^*a6}h2hnuXM+ym6Dw@ku-}B9|tSqSo zjbta)EeGpuD;4k7A2Rgz_^{%#GJG~%@ZSsVf30M^0Ld*IDd%y!1?rV*7u6<}x3WbU z?sC*+yy7!Op8STqRQ8k1Dsz%_k@kXv?+da-LJ6u@@rv2F!c__jwop+VHT=5ZA=7e~K_x5| zZQ;Lt^QjkeI-kiHG>gS10gpv=m!>qftY*+m7Aw1nkB1SjcuSyR7=9Z!fr{cXj2K%} zzC&Y!z^(k~k2ox}b={6*P%-P?>o~liSzaNfKnvdzee{{N|@UhWDTHu8eu{?)dUn2#)IGD~>6ksrH;X7SmS_poz1 zi!E&8vFH;jkKpK$3`%21L0=O;LZZgGc-~tyn?b28W-G-*%(_*H_rpo}Z;3Cw6>8YE ziQm;C^I85mUM0`gHl-RXN+-g$1G}F zFl2_Yvz3wrxVqv0mpOwCVY`6sl->ISs@GrRtfU3%&3F@QRq2(7mGoavk;4Hq>j|Lm zQl%dK1IW;Oe_85Nfu%= + @@ -14,192 +15,9 @@ Pörssisähkön hintaennuste - - +

Pörssisähkön hintaennuste

Tämä kokeellinen koneoppimismalli haarukoi pörssisähkön kuluttajahintaa noin 5 pv eteenpäin. Malli peilaa mm. ennustettua säätä, sähköntuotantoa ja nykyistä kuukautta/viikonpäivää aiempiin ajanjaksoihin, joissa nämä seikat olivat samankaltaiset.

@@ -209,10 +27,10 @@

Pörssisähkön hintaennuste

Lähipäivien hintakehitys

Teksti on luotu koneellisesti kielimallin avulla.

-

Toteutunut hinta vs. ennuste

-

Graafi kertoo, miten pörssihinta ja hinta-arvio vertautuvat toisiinsa: "Mitä tämän sivun malli arvaisi sähkön hinnaksi näissä samoissa olosuhteissa?" Vie hiiri graafin päälle tai siirrä/tiivistä/laajenna aikajanaa, niin näet lisää.

+

Toteutunut hinta vs. arvio

+

Graafi kertoo, miten pörssihinta ja mallin siitä oppima arvio vertautuvat toisiinsa: "Mitä tämän sivun malli arvaisi sähkön hinnaksi näissä menneissä olosuhteissa?" Vie hiiri graafin päälle tai siirrä/tiivistä/laajenna aikajanaa, niin näet lisää.

+

Huom: Tulevaisuudessa samat sää- ja tuotanto-olosuhteet saattavat johtaa aivan erilaiseen hintaan, jolloin malli ennusteineen on väärässä.

-

Tulevaisuudessa nämä samat olosuhteet saattavat johtaa erilaiseen hintaan, jolloin malli on ennusteineen väärässä.

Mitä ennuste huomioi?

Kyseessä on Random Forest -koneoppimismalli, joka hakee korrelaatioita eri muuttujien välillä ja oppii, millaiseen hintaan kukin yhdistelmä historian perusteella johtaa. Tämä ei siis ole aikasarjaennuste, vaan jokainen tunti saa arvonsa erikseen näiden tietojen perusteella:

  • Isoimpien tuulivoima-alueiden tuuliennuste: FMI-sääasemat, eli epäsuorasti tuulivoiman määrä
  • @@ -222,7 +40,8 @@

    Mitä ennuste huomioi?

  • Kellonaika: 0-23
  • Kuukausi: vuodenajan vaikutus, jos sellainen vaikutus on
  • Nämä muuttujat on valittu intuitiolla ja kokeilemalla. Jokin toinen yhdistelmä voisi toimia paremmin tai huonommin. Lähdekoodi on avointa, joten olet tervetullut kokeilemaan erilaisia vaihtoehtoja näiden lisäksi tai sijaan.

    -

    Ota huomioon, että sähkömarkkinoilla seilaa mustia joutsenia ja pörssisähkön ensi viikon hintaan ei tällä vatkaimella ole todellista näkyvyyttä, saati valtaa. Se lukee samoja sääennusteita kuin sinä, ja sääennusteet ovat tämän tästä väärässä. Vaikka malli osaa "ennustaa" menneisyyttä oivasti, se ei takaa että arviot tulevaisuudesta osuvat kohdalleen. Älä siis esim. säädä talosi lämmitystä näiden numeroiden perusteella, jos et ole kotona.

    +

    Ota huomioon, että sähkömarkkinoilla seilaa mustia joutsenia ja pörssisähkön ensi viikon hintaan ei tällä vatkaimella ole todellista näkyvyyttä, saati valtaa. Se lukee samoja sääennusteita kuin sinä, ja sääennusteetkin ovat tämän tästä väärässä. +

    Vaikka malli osaa "ennustaa" menneisyyttä oivasti, se ei takaa että arviot tulevaisuudesta osuvat kohdalleen. Älä siis esim. säädä talosi lämmitystä näiden numeroiden perusteella, jos et ole kotona.

    @@ -230,397 +49,6 @@

    Mitä ennuste huomioi?

    - - - - + diff --git a/deploy/index_dev.html b/deploy/index_dev.html new file mode 100644 index 000000000..afe92257c --- /dev/null +++ b/deploy/index_dev.html @@ -0,0 +1,720 @@ + + + + + + + + + + + + + + + + Pörssisähkön hintaennuste + + + +
    +
    +

    Pörssisähkön hintaennuste

    +

    Tämä kokeellinen koneoppimismalli haarukoi pörssisähkön kuluttajahintaa noin 5 pv eteenpäin. Malli peilaa mm. ennustettua säätä, sähköntuotantoa ja nykyistä kuukautta/viikonpäivää aiempiin ajanjaksoihin, joissa nämä seikat olivat samankaltaiset.

    +

    Vaikka malli on toisinaan yllättävänkin tarkka, sen tuottamaan ennusteeseen tulee suhtautua varauksella. Se ei huomioi tulevia poikkeuspäiviä, kuten esim. ydinvoimaloiden tai siirtoyhteyksien huoltokatkoja. Sääennuste voi vielä muuttua, ja tämä vaikuttaa lähipäivien hintaan. Ennuste päivittyy muutaman kerran päivässä uusimman datan pohjalta.

    +

    Nordpool-hinta näkyy liikennevalon väreillä. Mallin ennuste sinisellä katkoviivalla.

    +

    +
    + + +
    +

    Lähipäivien hintakehitys

    +

    Teksti on luotu koneellisesti kielimallin avulla.

    +

    +

    Toteutunut hinta vs. ennuste

    +

    Graafi kertoo, miten pörssihinta ja hinta-arvio vertautuvat toisiinsa: "Mitä tämän sivun malli arvaisi sähkön hinnaksi näissä samoissa olosuhteissa?" Vie hiiri graafin päälle tai siirrä/tiivistä/laajenna aikajanaa, niin näet lisää.

    +

    +

    Tulevaisuudessa nämä samat olosuhteet saattavat johtaa erilaiseen hintaan, jolloin malli on ennusteineen väärässä.

    +

    Mitä ennuste huomioi?

    +

    Kyseessä on Random Forest -koneoppimismalli, joka hakee korrelaatioita eri muuttujien välillä ja oppii, millaiseen hintaan kukin yhdistelmä historian perusteella johtaa. Tämä ei siis ole aikasarjaennuste, vaan jokainen tunti saa arvonsa erikseen näiden tietojen perusteella:

    +
  • Isoimpien tuulivoima-alueiden tuuliennuste: FMI-sääasemat, eli epäsuorasti tuulivoiman määrä
  • +
  • Muutaman keskeisen asutuskeskuksen lämpötilaennuste: FMI-sääasemat, eli epäsuorasti lämmitystarve
  • +
  • Ydinvoimatuotanto: Fingrid, huomioidaan usemman tunnin viiveellä, eli huoltokatkot eivät näy mallille etukäteen
  • +
  • Viikonpäivä: ma-su
  • +
  • Kellonaika: 0-23
  • +
  • Kuukausi: vuodenajan vaikutus, jos sellainen vaikutus on
  • +

    Nämä muuttujat on valittu intuitiolla ja kokeilemalla. Jokin toinen yhdistelmä voisi toimia paremmin tai huonommin. Lähdekoodi on avointa, joten olet tervetullut kokeilemaan erilaisia vaihtoehtoja näiden lisäksi tai sijaan.

    +

    Ota huomioon, että sähkömarkkinoilla seilaa mustia joutsenia ja pörssisähkön ensi viikon hintaan ei tällä vatkaimella ole todellista näkyvyyttä, saati valtaa. Se lukee samoja sääennusteita kuin sinä, ja sääennusteet ovat tämän tästä väärässä. Vaikka malli osaa "ennustaa" menneisyyttä oivasti, se ei takaa että arviot tulevaisuudesta osuvat kohdalleen. Älä siis esim. säädä talosi lämmitystä näiden numeroiden perusteella, jos et ole kotona.

    + + +
    + + +
    + + + + diff --git a/deploy/scripts.js b/deploy/scripts.js new file mode 100644 index 000000000..3530a4925 --- /dev/null +++ b/deploy/scripts.js @@ -0,0 +1,385 @@ + +if (window.location.hostname === "nordpool-predict-fi.web.app") { + window.location.href = "https://sahkovatkain.web.app" + window.location.pathname + window.location.search; +} + +// Hosted on GitHub, Firebase Hosting, or locally? +var baseUrl; + +switch (window.location.hostname) { + case "": + baseUrl = ""; + break; + case "nordpool-predict-fi.web.app": + baseUrl = "https://nordpool-predict-fi.web.app"; + break; + case "sahkovatkain.web.app": + baseUrl = "https://sahkovatkain.web.app"; + break; + default: + baseUrl = "https://raw.githubusercontent.com/vividfog/nordpool-predict-fi/main/deploy"; +} + +////////////////////////////////////////////////////////////////////////// +// Fetch the narration text from the Markdown file +fetch(`${baseUrl}/narration.md`) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.text(); + }) + .then(text => { + // LLM generated text in quotes + document.getElementById('narration').innerHTML = marked.parse("\"" + text + "\""); + }) + .catch(error => console.error('Fetching Markdown failed:', error)); + +////////////////////////////////////////////////////////////////////////// +// eCharts code for PREDICTION chart, including data from Sähkötin.fi +var nfpChart = echarts.init(document.getElementById('predictionChart')); + +// Calculate start and end dates for Sähkötin +var startDate = addDays(new Date(), -1).toISOString(); +var endDate = addDays(new Date(), 2).toISOString(); + +// URLs for the datasets +var npfUrl = `${baseUrl}/prediction.json`; // Using your existing baseUrl for NPF data +var sahkotinUrl = 'https://sahkotin.fi/prices.csv'; +var sahkotinParams = new URLSearchParams({ + fix: 'true', + vat: 'true', + start: startDate, + end: endDate, +}); + +// Helper function to calculate date ranges +function addDays(date, days) { + var result = new Date(date); + result.setHours(0, 0, 0, 0); + result.setDate(result.getDate() + days); + return result; +} + +// Fetch the data and display the chart +Promise.all([ + fetch(npfUrl).then(r => r.json()), // Fetch NPF data + fetch(`${sahkotinUrl}?${sahkotinParams}`).then(r => r.text()) // Fetch Sähkötin data +]) + .then(([npfData, sahkotinCsv]) => { + // Prepare NPF series data + var npfSeriesData = npfData.map(item => [item[0], item[1]]); + + // Prepare Sähkötin series data + var sahkotinSeriesData = sahkotinCsv.split('\n').slice(1).map(line => { + var [timestamp, price] = line.split(','); + return [new Date(timestamp).getTime(), parseFloat(price)]; + }); + + // Define the chart option with both series + nfpChart.setOption({ + title: { + text: ' ' + }, + legend: { + data: ['Nordpool', 'Ennuste'], + right: 16 + }, + tooltip: { + trigger: 'axis', + formatter: function (params) { + var result = params[0].axisValueLabel + '
    '; + params.forEach(function (item) { + // Round the value to one decimal place + var valueRounded = item.value[1].toFixed(1); + result += item.marker + " " + item.seriesName + ': ' + valueRounded + ' ¢/kWh
    '; + }); + return result; + } + }, + xAxis: { + type: 'time', + boundaryGap: false, + axisLabel: { + formatter: function (value) { + var date = new Date(value); + var weekdays = ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la']; + var year = date.getFullYear(); + var day = ("0" + date.getDate()).slice(-2); + var month = date.getMonth() + 1; // add 1 since getMonth() starts from 0 + var weekday = weekdays[date.getDay()]; + + return weekday + ' ' + day + '.'; + } + } + }, + yAxis: { + type: 'value', + name: '¢/kWh ALV24', + nameLocation: 'end', + nameGap: 20, + max: value => Math.ceil(value.max / 10) * 10, + nameTextStyle: { + fontWeight: 'regular' + }, + axisLabel: { + formatter: function (value) { + // Round the cents to one decimal place + return value.toFixed(0); + } + } + }, + + // Set gradient colors for the realized price + visualMap: [{ + show: false, + seriesIndex: [1], + top: 50, + right: 10, + pieces: [ + { lte: 5, color: 'lime' }, + { gt: 5, lte: 10, color: 'limegreen' }, + { gt: 10, lte: 15, color: 'gold' }, + { gt: 15, lte: 20, color: 'darkorange' }, + { gt: 20, lte: 30, color: 'red' }, + { gt: 30, color: 'darkred' } + ], + outOfRange: { + color: '#999' + } + }, + + // Set gradient colors for the predicted price + { + show: false, + seriesIndex: [0], + top: 50, + right: 10, + pieces: [ + { lte: 5, color: 'skyblue' }, + { gt: 5, lte: 10, color: 'deepskyblue' }, + { gt: 10, lte: 15, color: 'dodgerblue' }, + { gt: 15, lte: 20, color: 'blue' }, + { gt: 20, lte: 30, color: 'darkblue' }, + { gt: 30, color: 'midnightblue' } + ], + outOfRange: { + color: '#999' + } + }], + + // Data series + series: [ + { + // Prediction + name: 'Ennuste', + type: 'line', + data: npfSeriesData, + symbol: 'none', + lineStyle: { + type: 'dotted', + width: 3 + }, + }, + { + // Realized + name: 'Nordpool', + type: 'line', + data: sahkotinSeriesData, + symbol: 'none', + step: 'middle', + }, + { + // MarkLine for current time + type: 'line', + markLine: { + // Hides the symbol at the end of the line + symbol: 'none', + label: { + formatter: function () { + let currentTime = new Date(); + let hours = currentTime.getHours(); + let minutes = currentTime.getMinutes(); + + hours = hours < 10 ? '0' + hours : hours; + minutes = minutes < 10 ? '0' + minutes : minutes; + // This will be local time 24 hour formatted string + return 'klo ' + hours + ':' + minutes; + }, + position: 'end' + }, + lineStyle: { + type: 'dotted', + color: 'skyblue', + width: 1.5 + }, + data: [ + { + // Current time as the position for the line + xAxis: new Date().getTime() + } + ] + } + } + ] + }); + + // Match the time axis of the two series + var npfSeriesData = npfData.map(item => [item[0] * 1000, item[1]]); + + var sahkotinSeriesData = sahkotinCsv.split('\n').slice(1).map(line => { + var [timestamp, price] = line.split(','); + return [new Date(timestamp).getTime(), parseFloat(price)]; + }); + }) + .catch(error => { + console.error('Error fetching or processing data:', error); + }); + +// Function to update the vertical marker position +function updateMarkerPosition() { + console.log('A new minute, a new marker position!'); + var currentTime = new Date().getTime(); // Get current time in milliseconds + + // Update the markLine data for the current time marker + var option = nfpChart.getOption(); // Get the current chart options to overwrite + option.series.forEach((series) => { + if (series.markLine) { + series.markLine.data = [ + { + xAxis: currentTime + } + ]; + } + }); + + // Set the updated option to the chart without not merging + nfpChart.setOption(option, false, false); +} + +// Call the updateMarkerPosition function to update the marker +setInterval(updateMarkerPosition, 3000); // 3000 milliseconds = check every 3 seconds + +// END: eCharts code predictions + +////////////////////////////////////////////////////////////////////////// +// eCharts code for HISTORY chart +var pastPredictions = echarts.init(document.getElementById('pastPredictions')); + +// Fetch the data and display the chart +Promise.all([ + fetch(`${baseUrl}/past_performance.json`).then(r => r.json()) // Fetching past performance data +]) + .then(([jsonData]) => { + var actualPriceData = jsonData.data.find(series => series.name === "Actual Price").data + .map(item => [new Date(item[0]).getTime(), item[1]]); + var predictedPriceData = jsonData.data.find(series => series.name === "Predicted Price").data + .map(item => [new Date(item[0]).getTime(), item[1]]); + + pastPredictions.setOption({ + title: { + text: ' ' + }, + legend: { + data: ['Nordpool', 'Ennuste'], + right: 16 + }, + toolbox: { + // Not optimal on mobile, so disabled for now + // feature: { + // dataZoom: { + // yAxisIndex: 'none' + // }, + // restore: {}, + // saveAsImage: {} + // } + }, + dataZoom: [ + // { + // type: 'inside', + // start: 66, + // end: 100 + // }, + { + type: 'slider', + start: 66, + end: 100 + } + ], + tooltip: { + trigger: 'axis', + formatter: function (params) { + var result = params[0].axisValueLabel + '
    '; + params.forEach(function (item) { + var valueRounded = item.value[1].toFixed(1); + result += item.marker + " " + item.seriesName + ': ' + valueRounded + ' ¢/kWh
    '; + }); + return result; + } + }, + xAxis: { + type: 'time', + boundaryGap: false, + axisLabel: { + formatter: function (value) { + var date = new Date(value); + return date.toLocaleDateString('fi-FI'); + }, + rotate: 45, + + } + }, + yAxis: { + type: 'value', + name: '¢/kWh ALV24', + nameLocation: 'end', + nameGap: 20, + axisLabel: { + formatter: '{value}' + } + }, + series: [ + { + name: 'Nordpool', + type: 'line', + data: actualPriceData, + symbol: 'none', + symbolSize: 6, + smooth: true, + lineStyle: { + color: 'skyblue', + width: 1 + }, + itemStyle: { + color: 'skyblue' + } + }, + { + name: 'Ennuste', + type: 'line', + data: predictedPriceData, + symbol: 'none', + symbolSize: 6, + smooth: true, + lineStyle: { + color: 'darkorange', + width: 1 + }, + itemStyle: { + color: 'darkorange' + }, + + } + ] + }); + }) + .catch(error => { + console.error('Error fetching or processing data:', error); + }); +// END: eCharts code for history + +// Resize the chart when the window is resized +window.onresize = function () { + nfpChart.resize(); + pastPredictions.resize(); +} \ No newline at end of file diff --git a/deploy/styles.css b/deploy/styles.css new file mode 100644 index 000000000..0a438cd38 --- /dev/null +++ b/deploy/styles.css @@ -0,0 +1,204 @@ +body { + font-family: 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + color: #666; + margin: 20px; + background-color: #f0f2f5; + color: rgba(51, 51, 51, 0.9); +} + +#predictionChart { + height: 40vh; + width: 100%; + max-width: 920px; + margin: auto; + box-sizing: border-box; + padding-top: 32px; +} + +#chart { + max-width: 920px; + margin: auto; + box-sizing: border-box; +} + +#predictionText { + max-width: 920px; + line-height: 1.6; + box-sizing: border-box; + margin: auto; + margin-top: 16px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + background-color: #fff; + padding-left: 60px; + padding-right: 60px; + padding-top: 24px; + padding-bottom: 24px; +} + +#narration { + max-width: 920px; + margin: auto; + line-height: 1.6; + padding-bottom: 24px; + box-sizing: border-box; +} + +#llm { + max-width: 920px; + margin: auto; + text-align: left; + color: rgba(102, 102, 102, 0.7); + padding-top: 8px; + line-height: 1.5; + box-sizing: border-box; + font-size: 0.9em; + +} + +#pastPredictions { + height: 40vh; + width: 100%; + max-width: 920px; + margin: auto; + box-sizing: border-box; + padding-bottom: 24px; + padding-top: 24px; +} + +#disclaimer { + position: relative; + max-width: 512px; + line-height: 1.6; + margin: 16px auto 0; + color: #666; + font-size: 0.8em; + opacity: 0.6; + width: 100%; + padding: 4px 0; + text-align: center; +} + +#topdisclaimer { + position: relative; + max-width: 512px; + line-height: 1.6; + margin: auto; + color: #666; + font-size: 0.8em; + opacity: 0.6; + width: 100%; + padding: 16px; + padding-bottom: 8px; + text-align: center; +} + +#github-logo-container { + text-align: center; + width: 100%; + left: 0; + bottom: 0; + padding: 0px 0; + padding-top: 16px; + margin: 16px 0 4px 0; +} + +#source-code-text { + color: #666; + text-decoration: none; + font-size: 0.8em; + opacity: 0.5; + transition: opacity 0.15s ease-in-out; + text-align: center; + margin-bottom: 32px; +} + +#source-code-text:hover { + color: #666; + text-decoration: none; + // font-size: 0.8em; + opacity: 0.8; +} + +#github-logo { + height: 32px; + opacity: 0.5; + transition: opacity 0.15s ease-in-out; +} + +#github-logo:hover { + opacity: 0.8; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 200; + color: rgba(102, 102, 102, 0.8); + margin-bottom: 0; + line-height: 1.3; +} + +p { + margin-bottom: 1em; +} + +a { + color: #000; + text-decoration: underline; + opacity: 0.6; + border-bottom: 1px solid transparent; + font-weight: 400; + transition: opacity 0.15s ease-in-out; +} + +a:source-code-link { + text-decoration: none; +} + +a:hover { + text-decoration: underline; + color: #000; + opacity: 1; + font-weight: 500; +} + +li { + line-height: 1.6; + margin-bottom: 8px; + margin-left: 16px; + padding-left: 16px; + text-indent: -16px; +} + +@media (max-width: 768px) { + #body { + margin: 4px; + } + + #predictionText { + padding-left: 20px; + padding-right: 20px; + padding-top: 16px; + } + + #predictionChart { + height: 60vh; + } + + #pastPredictions { + height: 60vh; + } + + #disclaimer { + max-width: 320px; + } + + #topdisclaimer { + max-width: 320px; + padding: 8px; + padding-bottom: 4px; + } +} \ No newline at end of file