isTesting = false
data = transpose(f1_data)
initialUnits = localStorage.getItem("f1_units") === "Imperial"
// 2. Create the toggle
viewof units_toggle = Inputs.toggle({
label: "",
value: initialUnits
})
// 3. Define the units AND save to memory silently
units = {
const currentUnit = units_toggle ? "Imperial" : "Metric";
localStorage.setItem("f1_units", currentUnit); // This saves it quietly
return currentUnit; // This sends the value to the rest of your app
}
// --- WIND DIRECTION CALCULATOR ---
wind_cardinal = {
const degrees = weather_data.current.wind_direction_10m;
const sectors = ["N", "NE", "E", "SE", "S", "SW", "W", "NW", "N"];
// This math divides 360 into 8 chunks and picks the right label
return sectors[Math.round(degrees / 45) % 8];
}
// --- WEATHER DESCRIPTION DICTIONARY ---
weather_description = {
const code = weather_data.current.weather_code;
if (code === 0) return "Clear Skies";
if (code <= 3) return "Partly Cloudy";
if (code <= 48) return "Foggy Conditions";
if (code <= 57) return "Drizzle";
if (code <= 67) return "Rainy";
if (code <= 77) return "Snowy";
if (code <= 82) return "Rain Showers";
if (code <= 99) return "Thunderstorms";
return "Unknown Conditions";
}
// Convert that mapping into the emoji
weather_emoji = {
const code = weather_data.current.weather_code;
if (code === 0) return "☀️"; // Clear
if (code <= 3) return "☁️"; // Partly Cloudy/Cloudy
if (code <= 48) return "🌫️"; // Fog
if (code <= 57) return "🌦️"; // Drizzle
if (code <= 67) return "🌧️"; // Rain
if (code <= 77) return "❄️"; // Snow
if (code <= 82) return "🌧️"; // Showers
if (code <= 99) return "⛈️"; // Thunderstorms
return "🌡️";
}
// --- WEATHER LOGIC (Corrected Syntax) ---
weather_data = {
const cacheKey = "f1_weather_cache";
const cacheExpiry = 5 * 60 * 1000;
const now = Date.now();
const cached = JSON.parse(localStorage.getItem(cacheKey));
// 1. FAST PATH: If cache is fresh, RETURN IMMEDIATELY.
// No GPS hardware will be touched.
if (!isTesting && cached && (now - cached.timestamp < cacheExpiry)) {
return cached.data;
}
// 2. SLOW PATH: Only run if cache is dead or we are testing
if (isTesting) {
return { current: { temperature_2m: 21.5, relative_humidity_2m: 42, wind_speed_10m: 12.5, wind_direction_10m: 220, weather_code: 1, rain: 0 } };
} else {
// We only ask for GPS here, deep inside the 'else'
const pos = await new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject);
});
const w_res = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${pos.coords.latitude}&longitude=${pos.coords.longitude}¤t=temperature_2m,relative_humidity_2m,rain,weather_code,wind_speed_10m,wind_direction_10m&timezone=auto`);
const freshData = await w_res.json();
localStorage.setItem(cacheKey, JSON.stringify({
timestamp: now,
data: freshData
}));
return freshData;
}
}
// Map 3-letter F1 codes to 2-letter Flag codes
country_flag_url = {
const codes = {
"BRN": "bh", // Bahrain
"KSA": "sa", // Saudi Arabia
"AUS": "au", // Australia
"AZE": "az", // Azerbaijan
"USA": "us", // USA
"MON": "mc", // Monaco
"ESP": "es", // Spain
"CAN": "ca", // Canada
"AUT": "at", // Austria
"GBR": "gb", // UK
"HUN": "hu", // Hungary
"BEL": "be", // Belgium
"NED": "nl", // Netherlands
"ITA": "it", // Italy
"SGP": "sg", // Singapore
"JPN": "jp", // Japan
"MEX": "mx", // Mexico
"UAE": "ae", // UAE
"BRA": "br", // Brazil
"QAT": "qa", // Qatar
"CHN": "cn" // China
};
const cleanCode = match.country_code ? match.country_code.trim().toUpperCase() : "";
const twoLetter = codes[cleanCode];
// Return the URL for the flag image
return twoLetter
? `https://flagcdn.com/w40/${twoLetter.toLowerCase()}.png`
: "https://flagcdn.com/w40/un.png";
}
// --- MATCHING ENGINE ---
match = {
if (!weather_data || !data) return null;
const curr = weather_data.current;
const localTemp = curr.temperature_2m;
const col = "temp_C";
const v_rain = curr.rain > 0 ? "Rain" : "Dry";
let pool = data.filter(d => d.rain_label === v_rain);
if (pool.length === 0) pool = data;
let bestMatch = pool[0];
let minDistance = Infinity;
for (let d of pool) {
const dTemp = Math.abs((d[col] || 0) - localTemp) * 5;
const dHum = Math.abs(d.humidity - curr.relative_humidity_2m);
const dist = dTemp + dHum;
if (dist < minDistance) { minDistance = dist; bestMatch = d; }
}
return bestMatch;
}
html`
${(document.getElementById("loading-placeholder")?.remove(), "")}
<div style="display: flex; justify-content: center; width: 100%; overflow: hidden;">
<div style="
position: relative;
background: #1a1a1a;
padding: 20px;
border-radius: 15px;
border-top: 2px solid #e10600;
text-align: center;
margin: 5px;
font-family: 'Titillium Web', sans-serif;
color: white;
width: 100%;
max-width: 600px;
min-height: 550px; /* Reserves space to prevent jitter */
box-sizing: border-box;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
overflow: hidden;
">
<div style="position: absolute; top: 15px; right: 8px; z-index: 100;">
<div class="f1-toggle-container">${viewof units_toggle}</div>
</div>
${match ? html`
<div class="status-container" style="margin-bottom: 10px;">
<small style="color: #aaa; text-transform: uppercase; letter-spacing: 2px; font-weight: bold; display: flex; align-items: center;">
${isTesting ? html`⚠️ TEST MODE` : html`<span class="live-dot"></span> LIVE`}
</small>
</div>
<div style="font-size: 3.5em; margin-bottom: 5px;">
${weather_emoji}
</div>
<div style="color: #ffffff; text-transform: uppercase; font-size: 1.2em; letter-spacing: 2px; font-weight: 800; margin-bottom: 15px;">
${weather_description}
</div>
<div style="display: flex; justify-content: center; align-items: baseline; gap: 12px;">
<h2 style="margin: 0; font-size: 3.5em; white-space: nowrap;">
${units === "Metric"
? weather_data.current.temperature_2m + "°C"
: ((weather_data.current.temperature_2m * 9/5) + 32).toFixed(1) + "°F"}
</h2>
<span style="color: #e10600; font-size: 2.5em; font-weight: bold;">/</span>
<h2 style="margin: 0; font-size: 2em; white-space: nowrap;">
${weather_data.current.relative_humidity_2m}% <span style="font-size: 0.4em; color: #888;">HUM.</span>
</h2>
</div>
<div style="margin: 20px 0; display: flex; justify-content: center; gap: 40px;">
<div>
<small style="color: #888; text-transform: uppercase; font-size: 0.7em; display: block; letter-spacing: 1px;">Wind Speed</small>
<span style="font-weight: bold; font-size: 1.2em;">
${units === "Metric" ? weather_data.current.wind_speed_10m + " km/h" : (weather_data.current.wind_speed_10m * 0.621).toFixed(1) + " mph"}
</span>
</div>
<div>
<small style="color: #888; text-transform: uppercase; font-size: 0.7em; display: block; letter-spacing: 1px;"> Wind Direction</small>
<span style="font-weight: bold; font-size: 1.2em;">
${weather_data.current.wind_direction_10m}°
<span style="color: #e10600; margin-left: 5px;">${wind_cardinal}</span>
<span style="display: inline-block; transform: rotate(${weather_data.current.wind_direction_10m + 180}deg); color: #e10600; margin-right: 3px;">↑</span>
</span>
</div>
</div>
<div style="border-top: 1px solid #333; padding-top: 20px; margin-top: 20px;">
<p style="color: #e10600; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; font-size: 1.3em;">Similar weather felt:</p>
<div style="color: #fff; font-weight: 800; text-transform: uppercase; letter-spacing: 1px; font-size: 0.9em;">
${new Date(match.date_start).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}
</div>
<h1 style="font-size: 2.2em; font-weight: 900; font-style: italic; margin: 5px 0; text-transform: uppercase;">${match.location}</h1>
<div style="display: flex; justify-content: center; align-items: center; gap: 10px; margin-bottom: 15px;">
<img src="${country_flag_url}" style="height: 16px; border-radius: 2px;" />
<span style="color: #aaa; text-transform: uppercase; letter-spacing: 2px; font-size: 0.9em;">${match.country_name}</span>
</div>
<div style="background: #e10600; color: white; display: inline-block; padding: 4px 12px; border-radius: 4px; font-weight: bold; text-transform: uppercase; font-size: 0.8em;">
${match.session_name}
</div>
<p style="color: #aaa; font-size: 1em; font-style: italic; margin-top: 10px;">
Historical: ${units === "Metric"
? match.temp_C + "°C"
: ((match.temp_C * 9/5) + 32).toFixed(1) + "°F"} / ${match.humidity}% hum.
</p>
</div>
</div>
` : html`
<div style="padding: 100px 0;">
<div class="f1-spinner" style="border: 4px solid rgba(225,6,0,0.1); border-left-color: #e10600; border-radius: 50%; width: 40px; height: 40px; margin: 0 auto 20px; animation: f1-spin 1s linear infinite;"></div>
<p style="color: #e10600; font-weight: bold; letter-spacing: 2px;">CALCULATING TELEMETRY...</p>
</div>
`}
</div>
`Initializing One Formule...