frontend/src/routes/stocks/[tickerID]/forecast/ai/+page.svelte
2025-04-02 12:49:54 +02:00

1013 lines
40 KiB
Svelte

<script lang="ts">
import { displayCompanyName, stockTicker, screenWidth } from "$lib/store";
import { removeCompanyStrings } from "$lib/utils";
import Infobox from "$lib/components/Infobox.svelte";
import highcharts from "$lib/highcharts.ts";
import { mode } from "mode-watcher";
import SEO from "$lib/components/SEO.svelte";
export let data;
const isPro = ["Pro", "Plus"].includes(data?.user?.tier);
const formatDateToQuarter = (timestamp) => {
const date = new Date(timestamp);
const year = date.getFullYear().toString().slice(-2); // Get last two digits of the year
const quarter = Math.floor(date.getMonth() / 3) + 1; // Determine quarter (Q1-Q4)
return `Q${quarter} '${year}`;
};
const calculatePriceChange = (targetPrice) =>
targetPrice && price ? ((targetPrice / price - 1) * 100)?.toFixed(2) : 0;
function prepareDataset() {
price = data?.getStockQuote?.price?.toFixed(2) || 0;
avgPriceTarget = data?.getPriceAnalysis?.avgPriceTarget || 0;
medianPriceTarget = data?.getPriceAnalysis?.medianPriceTarget || 0;
lowPriceTarget = data?.getPriceAnalysis?.lowPriceTarget || 0;
highPriceTarget = data?.getPriceAnalysis?.highPriceTarget || 0;
lowChange = calculatePriceChange(lowPriceTarget);
medianChange = calculatePriceChange(medianPriceTarget);
avgChange = calculatePriceChange(avgPriceTarget);
highChange = calculatePriceChange(highPriceTarget);
// Assume data.getHistoricalPrice contains objects with a "time" field (e.g. "2015-01-02")
historicalData = data?.getHistoricalPrice || [];
backtestList = data?.getAIScore?.backtest || [];
// Append the latest historical date (using "time") if available. Note that this entry may not include a score.
if (historicalData && historicalData?.length) {
const latest = historicalData?.at(-1);
backtestList?.push({ date: latest.time });
const seenDates = new Set();
backtestList = backtestList?.filter((item) => {
// Check if the date is already seen
if (seenDates?.has(item?.date)) {
// If yes, skip this duplicate (delete the last one)
return false;
}
// Otherwise, record the date and keep the item
seenDates?.add(item?.date);
return true;
});
}
processedData = backtestList?.map((item) => {
const dateStr = item.date;
const targetTime = new Date(dateStr).getTime();
let closestPoint = historicalData[0];
let minDiff = Infinity;
historicalData.forEach((point) => {
const pointTime = new Date(point.time).getTime();
const diff = Math.abs(pointTime - targetTime);
if (diff < minDiff) {
minDiff = diff;
closestPoint = point;
}
});
// Base data point with x as the target date timestamp and y as the close price from the closest historical point
const dataPoint = { x: targetTime, y: closestPoint.close };
// If a score is provided, add marker configuration based on its value.
if (item.hasOwnProperty("score")) {
let markerColor, markerSymbol;
if (item.score > 6) {
// Bullish: green marker with an upward triangle
markerColor = "#007050";
markerSymbol = "circle";
} else if (item.score < 5) {
// Bearish: red marker with a downward triangle
markerColor = "#007050";
markerSymbol = "circle";
} else {
// Neutral (score exactly 5): yellow marker with a circle
markerColor = "#007050";
markerSymbol = "circle";
}
dataPoint.marker = {
symbol: markerSymbol,
radius: 4,
fillColor: markerColor,
lineWidth: 2,
lineColor: $mode === "light" ? "black" : "white",
};
dataPoint.dataLabels = {
enabled: true,
format: String(item.score),
style: {
color: $mode === "light" ? "black" : "white",
fontWeight: "bold",
fontSize: "14px",
},
y: -10,
};
}
return dataPoint;
});
tableDates = processedData
?.slice(0, -1)
?.map((item) => formatDateToQuarter(item?.x));
tableScore = processedData
?.slice(0, -1)
?.map((item) => item?.dataLabels?.format);
// Compute percentage change
tableQuarterChange = processedData
?.slice(0, -1)
?.map((item, index, arr) => {
const prevY = arr[index - 1]?.y; // Get the previous value
if (prevY == null || item.y == null) return null; // Handle missing values
const change = ((item.y - prevY) / prevY) * 100; // Calculate percentage change
return {
quarter: tableDates[index],
change: Number(change?.toFixed(2)), // Format to 2 decimal places
};
})
?.filter(Boolean); // Remove null values
// Compute Average Return
returns = processedData
?.slice(1) // Skip the first value since there's no previous value for it
?.map((item, index) => {
const prevY = processedData[index]?.y;
if (prevY == null || item.y == null) return null;
const returnPercentage = ((item.y - prevY) / prevY) * 100;
return returnPercentage;
})
.filter(Boolean); // Remove null values
avgReturn =
returns?.reduce((sum, returnPercentage) => sum + returnPercentage, 0) /
returns?.length;
}
let price;
let avgPriceTarget;
let medianPriceTarget;
let lowPriceTarget;
let highPriceTarget;
let lowChange;
let medianChange;
let avgChange;
let highChange;
let historicalData;
let backtestList;
// For each backtest entry, find the historical price with the closest available time.
// Then, if a score exists, attach a marker and data label based on the score.
let processedData = [];
let tableDates;
let tableScore;
// Compute percentage change
let tableQuarterChange;
// Compute Average Return
let returns;
let avgReturn;
function getAIScorePlot() {
const solidData = processedData.slice(0, -1);
const lastTwoPoints = processedData.slice(-2); // Extract the last two points
// Highcharts options for plotting the data with markers.
const options = {
chart: {
backgroundColor: $mode === "light" ? "#fff" : "#09090B",
height: 360,
animation: false,
},
title: {
text: `<h3 class="mt-3 mb-1 text-[1rem] sm:text-lg">Historical AI Score Performance</h3>`,
style: {
color: $mode === "light" ? "black" : "white",
// Using inline CSS for margin-top and margin-bottom
},
useHTML: true, // Enable HTML to apply custom class styling
},
xAxis: {
gridLineWidth: 1,
gridLineColor: $mode === "light" ? "#e5e7eb" : "#111827",
type: "datetime",
labels: {
style: {
color: $mode === "light" ? "#545454" : "white",
},
formatter: function () {
const date = new Date(this.value);
return date.toLocaleDateString("en-US", {
month: "short",
year: "numeric",
});
},
},
},
tooltip: {
enabled: false,
},
yAxis: {
title: {
text: "",
},
labels: {
style: {
color: $mode === "light" ? "#545454" : "white",
},
formatter: function () {
return `$${this.value.toFixed(0)}`;
},
},
gridLineWidth: 1,
gridLineColor: $mode === "light" ? "#e5e7eb" : "#111827",
},
series: [
{
name: "Close Price",
data: solidData,
color: $mode === "light" ? "#007050" : "#fff",
animation: false,
marker: {
enabled: true,
},
lineWidth: 3,
},
{
name: "Projected Price",
data: lastTwoPoints, // Include the second-last and last point
color: $mode === "light" ? "#007050" : "#fff",
animation: false,
marker: {
enabled: false,
},
lineWidth: 2.5,
dashStyle: "Dash", // Dashed line for the last part
},
],
plotOptions: {
series: {
enableMouseTracking: false,
states: {
hover: {
enabled: false,
},
},
marker: {
states: {
hover: {
enabled: false,
},
},
},
},
},
legend: {
enabled: false,
},
credits: {
enabled: false,
},
};
return options;
}
function getPriceForecastChart() {
const historicalData = data?.getAnalystSummary?.pastPriceList || [];
const forecastTargets = {
low: lowPriceTarget,
avg: avgPriceTarget,
high: highPriceTarget,
};
// Process historical data
const processedHistorical = historicalData?.map((point) => [
new Date(point?.date).getTime(),
point?.close,
]);
const currentDate = new Date();
const forecastDate = new Date(
currentDate.getFullYear() + 1,
currentDate.getMonth(),
currentDate.getDate(),
);
const forecastTimestamp = forecastDate.getTime();
// Get the last historical data point
const lastHistoricalDate = new Date(
historicalData[historicalData.length - 1]?.date,
).getTime();
const lastHistoricalClose =
historicalData[historicalData.length - 1]?.close;
// Create forecast points
const forecastHigh = [
[lastHistoricalDate, lastHistoricalClose],
[forecastTimestamp, forecastTargets.high],
];
const forecastAvg = [
[lastHistoricalDate, lastHistoricalClose],
[forecastTimestamp, forecastTargets.avg],
];
const forecastLow = [
[lastHistoricalDate, lastHistoricalClose],
[forecastTimestamp, forecastTargets.low],
];
const options = {
tooltip: {
enabled: false,
},
plotOptions: {
series: {
enableMouseTracking: false,
states: {
hover: {
enabled: false,
},
},
marker: {
states: {
hover: {
enabled: false,
},
},
},
},
},
chart: {
backgroundColor: $mode === "light" ? "#fff" : "#09090B",
plotBackgroundColor: $mode === "light" ? "#fff" : "#09090B",
height: 360,
animation: false,
},
title: {
text: `<div class="grid grid-cols-2 w-[200px] sm:w-[500px] -mb-3.5 text-xs font-[501] text-gray-600 dark:text-gray-400">
<h3 class="text-left">${$screenWidth && $screenWidth < 640 ? "Past Year" : "Past 12 Months"}</h3>
<h3 class="text-right">${$screenWidth && $screenWidth < 640 ? "Next Year" : "12 Month Forecast"}</h3>
</div>`,
style: {
color: $mode === "light" ? "black" : "white",
width: "100%",
},
verticalAlign: "top",
useHTML: true,
},
xAxis: {
gridLineWidth: 1,
gridLineColor: $mode === "light" ? "#e5e7eb" : "#111827",
type: "datetime",
endOnTick: false,
labels: {
style: {
color: $mode === "light" ? "#545454" : "white",
},
formatter: function () {
const date = new Date(this.value);
return date.toLocaleDateString("en-US", {
month: "short",
year: "numeric",
});
},
},
},
yAxis: {
title: {
text: "",
},
labels: {
style: {
color: $mode === "light" ? "#545454" : "white",
},
formatter: function () {
return `$${this.value.toFixed(0)}`;
},
},
gridLineWidth: 1,
gridLineColor: $mode === "light" ? "#e5e7eb" : "#111827",
},
series: [
{
animation: false,
name: "Historical",
data: processedHistorical,
color: $mode === "light" ? "#007050" : "#fff",
marker: {
enabled: true,
symbol: "circle",
radius: 4,
},
lineWidth: 3,
},
{
animation: false,
name: "High",
data: forecastHigh,
color: "#31B800",
dashStyle: "Dash",
marker: {
enabled: false,
},
lineWidth: 2.5,
},
{
animation: false,
name: "Average",
data: forecastAvg,
color: $mode === "light" ? "#007050" : "#fff",
dashStyle: "Dash",
marker: {
enabled: false,
},
lineWidth: 2.5,
},
{
animation: false,
name: "Low",
data: forecastLow,
color: "#D9220E",
dashStyle: "Dash",
marker: {
enabled: false,
},
lineWidth: 2.5,
},
],
legend: {
enabled: false,
},
credits: {
enabled: false,
},
};
return options;
}
let config = null;
let configScore = null;
$: {
if ($stockTicker || $mode) {
prepareDataset();
configScore = getAIScorePlot() || null;
config = getPriceForecastChart() || null;
}
}
</script>
<SEO
title={`${$displayCompanyName} (${$stockTicker}) Stock Forecast & Analyst Ratings | AI Insights`}
description={`Discover our AI-powered forecast for ${$displayCompanyName} (${$stockTicker}). Get in-depth analyst ratings, price targets, upgrades, and downgrades from top Wall Street experts. Stay ahead of market trends and make smarter investment decisions.`}
/>
<section class="w-full overflow-hidden h-full">
<div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
<div
class="w-full relative flex justify-center items-center overflow-hidden"
>
<main class="w-full">
<div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0">
{#if data?.getAIScore?.backtest?.length > 0}
<div class="">
<h1 class="text-xl sm:text-2xl font-bold">
{removeCompanyStrings($displayCompanyName)} AI Score Forecast
</h1>
</div>
<div class="w-full mb-10 mt-3">
<div
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-4 mt-3"
>
<div
class="shadow-md bg-gray-100 dark:bg-gray-800/30 rounded-lg p-4"
>
<div class="text-sm sm:text-[1rem] mb-2 flex items-center">
<span>Score Accuracy</span>
</div>
<div class="flex items-baseline">
<span class="text-xl font-bold"
>{data?.getAIScore?.accuracy
? data?.getAIScore?.accuracy + "%"
: "n/a"}</span
>
</div>
</div>
<div
class="shadow-md bg-gray-100 dark:bg-gray-800/30 rounded-lg p-4"
>
<div class="text-sm sm:text-[1rem] mb-2 flex items-center">
<span>Latest Forecast</span>
</div>
<div class="flex items-baseline">
<span class="text-xl font-bold">
{#if isPro}
{[10, 9, 8, 7]?.includes(data?.getAIScore?.score)
? "Bullish"
: [6, 5, 4]?.includes(data?.getAIScore?.score)
? "Hold"
: "Bearish"}
{:else}
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>Pro <svg
class="w-5 h-5 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg></a
>
{/if}
</span>
</div>
</div>
<div
class="shadow-md bg-gray-100 dark:bg-gray-800/30 rounded-lg p-4"
>
<div class="text-sm sm:text-[1rem] mb-2 flex items-center">
<span>Avg Return</span>
</div>
<div class="flex items-baseline">
{#if isPro}
<span
class="text-xl font-bold {avgReturn >= 0
? "before:content-['+'] text-green-800 dark:text-[#00FC50]"
: 'text-red-800 dark:text-[#FF2F1F]'}"
>{avgReturn?.toFixed(2)}%</span
>
{:else}
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400 text-xl font-bold"
>
Pro <svg
class="w-5 h-5 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg></a
>
{/if}
</div>
</div>
</div>
<div>
<div class="grow">
<div class="relative">
<!-- Apply the blur class to the chart -->
<div
class="{!isPro
? 'blur-[3px]'
: ''} mt-5 shadow-sm sm:mt-0 sm:border sm:border-gray-300 dark:border-gray-800 rounded"
use:highcharts={configScore}
></div>
<!-- Overlay with "Upgrade to Pro" -->
{#if !["Pro", "Plus"]?.includes(data?.user?.tier)}
<div
class="font-bold text-lg sm:text-xl absolute top-0 bottom-0 left-0 right-0 flex items-center justify-center text-muted dark:text-white"
>
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-white dark:text-white flex flex-row items-center"
>
<span>Upgrade to Pro</span>
<svg
class="ml-1 w-5 h-5 sm:w-6 sm:h-6 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/></svg
>
</a>
</div>
{/if}
</div>
<div
class="no-scrollbar mb-1 mt-2 overflow-x-auto px-1.5 text-center md:mb-0 md:px-0 lg:mt-5"
>
<table
class="table table-sm table-compact w-full text-right text-tiny xs:text-sm"
>
<thead>
<tr
class="border-b border-gray-300 dark:border-gray-600 font-normal text-sm sm:text-[1rem] whitespace-nowrap"
>
<th
class="py-[3px] text-left font-semibold lg:py-0.5 text-muted dark:text-white"
>
Date
</th>
{#each tableDates as item}
<th
class="py-[3px] text-left font-semibold lg:py-0.5 text-muted dark:text-white"
>
{item}
</th>
{/each}
</tr>
</thead>
<tbody>
<!-- Score Row -->
<tr
class="border-b border-gray-300 dark:border-gray-600 font-normal text-sm sm:text-[1rem] whitespace-nowrap"
>
<td class="py-[3px] text-left lg:py-0.5 text-[1rem]"
>Score</td
>
{#each tableScore as val, index}
<td
class="text-right whitespace-nowrap text-[1rem]"
>
{#if index < 5 || isPro}
{val}
{[10, 9, 8, 7].includes(Number(val))
? "(Bullish)"
: [6, 5, 4].includes(Number(val))
? "(Hold)"
: "(Sell)"}
{:else}
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>
Pro
<svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg>
</a>
{/if}
</td>
{/each}
</tr>
<!-- QoQ Change Row -->
<tr
class="font-normal text-sm sm:text-[1rem] whitespace-nowrap"
>
<td class="py-[3px] text-left lg:py-0.5 text-[1rem]"
>QoQ Change</td
>
{#each tableQuarterChange as item, index}
<td class="text-[1rem]">
{#if index < 5 || isPro}
<span
class={item?.change > 0
? "before:content-['+'] text-green-800 dark:text-[#00FC50]"
: "text-red-800 dark:text-[#FF2F1F]"}
>
{item?.change}%
</span>
{:else}
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>
Pro
<svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg>
</a>
{/if}
</td>
{/each}
</tr>
</tbody>
</table>
</div>
<p class="mt-4">
Following the AI Score for {removeCompanyStrings(
$displayCompanyName,
)} the model shows that the average return would be
<span
class="font-semibold {avgReturn >= 0
? "before:content-['+'] text-green-800 dark:text-[#00FC50]"
: 'text-red-800 dark:text-[#FF2F1F]'}"
>{avgReturn?.toFixed(2)}%</span
> based on the backtesting results.
</p>
</div>
</div>
</div>
{/if}
{#if Object?.keys(data?.getPriceAnalysis)?.length > 0}
<div class="">
<h1 class="text-xl sm:text-2xl font-bold">
{removeCompanyStrings($displayCompanyName)} Trend Forecast
</h1>
</div>
<div class="w-full mb-6 mt-3">
{#if !["Pro", "Plus"]?.includes(data?.user?.tier)}
<Infobox
text={`Using our AI model trained on historical seasonal data, we generated a 12-month forecast for ${removeCompanyStrings($displayCompanyName)}. The model predicts a ... Unlock content with
<a
class="inline-block ml-0.5 text-blue-700 sm:hover:text-muted dark:text-blue-400 dark:sm:hover:text-white"
href="/pricing"
>Pro Subscription <svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/></svg
></a
>`}
/>
{:else}
<Infobox
text={`Using our AI model trained on historical seasonal data, we generated a 12-month forecast for ${removeCompanyStrings($displayCompanyName)}. The model predicts a median target price of ${medianPriceTarget}, ranging from ${lowPriceTarget} to ${highPriceTarget}, indicating a ${medianChange > 0 ? "potential increase" : "potential decrease"} of ${medianChange}% from the current price of ${price}.`}
/>
{/if}
<div>
<div class="grow pt-5">
<div class="relative">
<!-- Apply the blur class to the chart -->
<div
class="{!isPro
? 'blur-[3px]'
: ''} mt-5 shadow-sm sm:mt-0 sm:border sm:border-gray-300 dark:border-gray-800 rounded"
use:highcharts={config}
></div>
<!-- Overlay with "Upgrade to Pro" -->
{#if !["Pro", "Plus"]?.includes(data?.user?.tier)}
<div
class="font-bold text-lg sm:text-xl absolute top-0 bottom-0 left-0 right-0 flex items-center justify-center text-muted dark:text-white"
>
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-white dark:text-white flex flex-row items-center"
>
<span>Upgrade to Pro</span>
<svg
class="ml-1 w-5 h-5 sm:w-6 sm:h-6 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/></svg
>
</a>
</div>
{/if}
</div>
<div
class="hide-scroll mb-1 mt-2 overflow-x-auto px-1.5 text-center md:mb-0 md:px-0 lg:mt-5"
>
<table class="w-full text-right text-tiny xs:text-sm">
<thead
><tr
class="border-b border-gray-300 dark:border-gray-600 font-normal text-sm sm:text-[1rem]"
><th
class="py-[3px] text-left font-semibold lg:py-0.5"
>Target</th
> <th class="font-semibold">Low</th>
<th class="font-semibold">Average</th>
<th class="font-semibold">Median</th>
<th class="font-semibold">High</th></tr
></thead
>
<tbody
><tr
class="border-b border-gray-300 dark:border-gray-600 font-normal text-sm sm:text-[1rem]"
>
<td class="py-[3px] text-left lg:py-0.5">Price</td>
{#if !["Pro", "Plus"]?.includes(data?.user?.tier)}
<td class="whitespace-nowrap">
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>Pro <svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg></a
>
</td>
<td class="whitespace-nowrap">
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>Pro <svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg></a
>
</td>
<td class="whitespace-nowrap">
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>Pro <svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg></a
>
</td>
<td class="whitespace-nowrap">
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>Pro <svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg></a
>
</td>
{:else}
<td>${lowPriceTarget}</td>
<td>${avgPriceTarget}</td>
<td>${medianPriceTarget}</td>
<td>${highPriceTarget}</td>
{/if}
</tr>
<tr class="text-sm sm:text-[1rem]">
<td class="py-[3px] text-left lg:py-0.5">Change</td>
{#if !["Pro", "Plus"]?.includes(data?.user?.tier)}
<td class="whitespace-nowrap">
<a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>
Pro
<svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg>
</a>
</td>
<td class="whitespace-nowrap"
><a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>Pro <svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg></a
></td
>
<td class="whitespace-nowrap"
><a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>Pro <svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg></a
></td
>
<td class="whitespace-nowrap"
><a
href="/pricing"
class="sm:hover:text-blue-700 dark:sm:hover:text-blue-400"
>Pro <svg
class="w-4 h-4 mb-1 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/>
</svg></a
></td
>
{:else}
<td
class={lowChange > 0
? "before:content-['+'] text-green-800 dark:text-[#00FC50]"
: "text-red-800 dark:text-[#FF2F1F]"}
>{lowChange}%</td
>
<td
class={avgChange > 0
? "before:content-['+'] text-green-800 dark:text-[#00FC50]"
: "text-red-800 dark:text-[#FF2F1F]"}
>{avgChange}%</td
>
<td
class={medianChange > 0
? "before:content-['+'] text-green-800 dark:text-[#00FC50]"
: "text-red-800 dark:text-[#FF2F1F]"}
>{medianChange}%</td
>
<td
class={highChange > 0
? "before:content-['+'] text-green-800 dark:text-[#00FC50]"
: "text-red-800 dark:text-[#FF2F1F]"}
>{highChange}%</td
>
{/if}
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
{:else}
<Infobox text="No AI Price Forecast available right now" />
{/if}
</div>
</main>
</div>
</div>
</section>