Hey, I find it very handy those separate sensors, a lot of freedom to make your own dashboard cards. I made the Resuls card here and will do the rest, so far without problems.
You can click on that chevron-up icon to make the map expand and show the rest of the race results. The dashboard cards will update themselves if the race ends after 00:00. The current race is indicated by a glow effect. The cards automatically adjust when there is a sprint race.
You need custom button card mod (use hacs)
Here the code for the F1 Schedule 2025
Make a “input_boolean.f1_race_toggle” so you can expand the card when you click on it.
type: custom:button-card
entity: sensor.f12025_current_season
icon: mdi:calendar-clock
show_name: false
show_state: false
show_icon: false
layout: custom
tap_action:
action: call-service
service: input_boolean.toggle
service_data:
entity_id: input_boolean.f1_race_toggle
styles:
grid:
- grid-template-areas: |
"header"
"races"
- row-gap: 12px
card:
- padding: 12px
- border-radius: 8px
- background: linear-gradient(135deg,
- color: white
- font-family: sans-serif
- box-shadow: 0px 4px 10px rgba(0,0,0,0.3)
custom_fields:
header:
- font-size: 17px
- font-weight: 600
- text-align: center
- padding-bottom: 6px
- border-bottom: 1px solid rgba(255,255,255,0.2)
- cursor: pointer
races:
- font-size: 14px
- line-height: 1.6
- display: flex
- flex-direction: column
- gap: 6px
custom_fields:
header: |
[[[
if (!entity?.attributes) return "Race calendar not available";
const expanded = states['input_boolean.f1_race_toggle']?.state === 'on';
const icon = expanded ? "mdi:chevron-up" : "mdi:chevron-down";
return `
<div style="width: 100%; text-align: center;">
<div style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer;">
<span style="font-weight: 600;">🏁 F1 Schedule - ${entity.attributes.season}</span>
<ha-icon icon="${icon}" style="--mdc-icon-size: 20px;"></ha-icon>
</div>
</div>
`;
]]]
races: |
[[[
if (!entity?.attributes?.races) return "No races found";
const toggle = states['input_boolean.f1_race_toggle']?.state === 'on';
const races = toggle ? entity.attributes.races : entity.attributes.races.slice(0, 10);
const now = new Date();
let nextRace = null;
for (const r of entity.attributes.races) {
const raceDate = new Date(r.date + 'T' + (r.time || '00:00:00Z'));
if (raceDate > now) {
nextRace = r;
break;
}
}
return races.map((r, idx) => {
const date = new Date(r.date + 'T' + (r.time || '00:00:00Z'));
const dateString = date.toLocaleDateString('nl-NL', { weekday: 'short', day: 'numeric', month: 'short' });
const timeString = date.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit' });
const isNextRace = r === nextRace;
const bg = isNextRace
? 'rgba(255, 215, 0, 0.12)'
: (idx % 2 === 0 ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.08)');
return `
<div style="display: flex; flex-direction: column; background: ${bg}; padding: 6px 10px; border-radius: 4px;">
<div><strong>Ronde ${r.round}:</strong> ${r.raceName}</div>
<div style="font-size: 12px; color: var(--secondary-text-color);">
📍 ${r.Circuit.Location.locality}, ${r.Circuit.Location.country} • ${dateString} ${r.time ? '• ' + timeString : ''}
</div>
</div>`;
}).join('');
]]]
Here the code for the Last Grand Prix
Make a “input_boolean.f1_race_results” so you can expand the card when you click on it.
type: custom:button-card
entity: sensor.f12025_last_race_results
icon: mdi:trophy
show_name: false
show_state: false
show_icon: false
layout: custom
tap_action:
action: call-service
service: input_boolean.toggle
service_data:
entity_id: input_boolean.f1_race_results
styles:
grid:
- grid-template-areas: |
"header"
"results"
- row-gap: 12px
card:
- padding: 12px
- border-radius: 8px
- background: linear-gradient(135deg,
- color: white
- font-family: Arial
- box-shadow: 0px 4px 10px rgba(0,0,0,0.3)
custom_fields:
header:
- font-size: 17px
- font-weight: 600
- text-align: center
- padding-bottom: 6px
- border-bottom: 1px solid rgba(255,255,255,0.2)
- cursor: pointer
results:
- font-size: 14px
- line-height: 1.6
- display: flex
- flex-direction: column
- gap: 6px
custom_fields:
header: |
[[[
const race = entity.attributes?.race_name || 'Last Race';
const round = entity.attributes?.round || '?';
const icon = states['input_boolean.f1_race_results']?.state === 'on' ? "mdi:chevron-up" : "mdi:chevron-down";
return `
<div style="width: 100%; text-align: center;">
<div style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer;">
<span style="font-weight: 600;">🏁 ${race.replace('Grand Prix', 'GP')} – Round ${round}</span>
<ha-icon icon="${icon}" style="--mdc-icon-size: 20px;"></ha-icon>
</div>
</div>
`;
]]]
results: |
[[[
const results = entity.attributes?.results || [];
const expanded = states['input_boolean.f1_race_results']?.state === 'on';
const displayResults = expanded ? results : results.slice(0, 10);
const teamColors = {
"Red Bull": "#1E41FF",
"Ferrari": "#DC0000",
"Mercedes": "#00D2BE",
"McLaren": "#FF8700",
"Aston Martin": "#006F62",
"Alpine F1 Team": "#0090FF",
"RB F1 Team": "#6699FF",
"Haas F1 Team": "#B6BABD",
"Williams": "#005AFF",
"Sauber": "#52E252"
};
return displayResults.map((r, idx) => {
const pos = r.position;
const code = r.driver?.code || '';
const name = `${r.driver?.givenName || ''} ${r.driver?.familyName || ''}`.trim();
const team = r.constructor?.name || '';
const color = teamColors[team] || '#888';
const points = r.points || '0';
const bg = idx % 2 === 0 ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.08)';
return `
<div style="display: flex; align-items: center; background: ${bg}; padding: 6px 10px; border-radius: 4px;">
<div style="width: 4px; height: 28px; background: ${color}; border-radius: 2px; margin-right: 8px;"></div>
<div style="flex: 1">
<div style="color: var(--primary-text-color);"><strong>${pos}.</strong> <strong>${code}</strong> - ${name}</div>
<div style="font-size: 12px; color: var(--secondary-text-color);">${team} • ${points} pt${points == 1 ? '' : 's'}</div>
</div>
</div>`;
}).join('');
]]]
Here the code for the Next Grand Prix
Make a “input_boolean.f1_race_results” so you can expand the card when you click on it.
type: custom:mod-card
style:
ha-card:
border-radius: 16px
overflow: hidden
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.4)
background: linear-gradient(135deg,
padding: 0
color: white
card:
type: vertical-stack
cards:
- type: custom:button-card
entity: sensor.f12025_next_race
show_name: false
show_state: false
show_icon: false
layout: custom
styles:
grid:
- grid-template-areas: |
"header"
"details"
- row-gap: 10px
card:
- padding: 16px
- background: linear-gradient(135deg,
- color: white
- font-family: sans-serif
- border-radius: 16px 16px 0 0
custom_fields:
header:
- font-size: clamp(16px, 2vw, 18px)
- font-weight: 600
- text-align: center
- padding: 0 8px 8px 8px
- border-bottom: 1px solid rgba(255,255,255,0.2)
- white-space: normal
- word-break: break-word
details:
- font-size: 14px
- line-height: 1.7
- display: flex
- flex-direction: column
- gap: 6px
custom_fields:
header: |
[[[
if (!entity?.attributes) return "Next GP not available";
const race = entity.attributes.race_name;
const start = new Date(entity.attributes.race_start);
const end = new Date(start.getTime() + 90 * 60 * 1000);
const now = new Date();
const diff = start - now;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
let status = "";
if (now < start) {
status = `⏳ In ${days}d ${hours}u ${minutes}m`;
} else if (now >= start && now <= end) {
status = "🏁 Race has started!";
} else {
status = "✅ The race is over";
}
return `
<div style="font-size: clamp(16px, 2vw, 18px); font-weight: 600; text-align: center;">🏁 NEXT GRAND PRIX</div>
<div style="font-size: clamp(14px, 1.6vw, 15px); font-weight: 500; text-align: center; margin-top: 4px; color: #ccc;">${race}</div>
<div style="margin-top: 8px; font-size: clamp(14px, 1.8vw, 16px); font-weight: 600; text-align: center; color: #f5c518;">${status}</div>
`;
]]]
details: |
[[[
const attrs = entity.attributes;
if (!attrs) return "";
const now = new Date();
const start = new Date(attrs.race_start);
const diff = start - now;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const countdown = diff > 0
? `⏳ In ${days}d ${hours}u ${minutes}m`
: "🏁 Race has started!";
const formatSession = (label, datetime, index) => {
if (!datetime) return '';
const dt = new Date(datetime);
const now = new Date();
const durations = {
'🧪 FP1': 65,
'🧪 FP2': 65,
'🧪 FP3': 65,
'🛞 Qualifying': 75,
'🏁 Race': 105,
'📐 Sprint Quali': 75,
'🏃 Sprint': 50,
};
const duration = durations[label] || 60;
const end = new Date(dt.getTime() + duration * 60 * 1000);
const isLive = now >= dt && now <= end;
const bg = index % 2 === 0 ? 'rgba(255,255,255,0.05)' : 'rgba(255,255,255,0.1)';
const liveStyle = isLive
? `background: rgba(224, 195, 0, 0.2);
animation: pulse 1.5s infinite;
border: 2px solid rgba(224, 195, 0, 0.8);`
: `background: ${bg};`;
const day = dt.toLocaleDateString('nl-NL', { weekday: 'short', day: 'numeric', month: 'short' });
const time = dt.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit' });
return `
<style>
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 223, 0, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(255, 223, 0, 0); }
100% { box-shadow: 0 0 0 0 rgba(255, 223, 0, 0); }
}
</style>
<div style="${liveStyle} padding: 6px 8px; border-radius: 8px;">
${label}: <strong>${day} • ${time}</strong>
</div>`;
};
let index = 0;
const rows = [
`<div style="background: ${index++ % 2 === 0 ? 'rgba(255,255,255,0.05)' : 'rgba(255,255,255,0.1)'}; padding: 6px 8px; border-radius: 8px;"><strong>🏟️ Circuit:</strong> ${attrs.circuit_name}</div>`,
`<div style="background: ${index++ % 2 === 0 ? 'rgba(255,255,255,0.05)' : 'rgba(255,255,255,0.1)'}; padding: 6px 8px; border-radius: 8px;"><strong>📍 Location:</strong> ${attrs.circuit_locality}, ${attrs.circuit_country}</div>`,
`<div style="background: ${index++ % 2 === 0 ? 'rgba(255,255,255,0.05)' : 'rgba(255,255,255,0.1)'}; padding: 6px 8px; border-radius: 8px;"><strong>📆 Race date:</strong> ${start.toLocaleDateString('nl-NL', { weekday: 'short', day: 'numeric', month: 'long' })}</div>`
];
rows.push(formatSession('🧪 FP1', attrs.first_practice_start, index++));
if (attrs.sprint_qualifying_start || attrs.sprint_start) {
rows.push(formatSession('📐 Sprint Quali', attrs.sprint_qualifying_start, index++));
rows.push(formatSession('🏃 Sprint', attrs.sprint_start, index++));
} else {
rows.push(formatSession('🧪 FP2', attrs.second_practice_start, index++));
rows.push(formatSession('🧪 FP3', attrs.third_practice_start, index++));
}
rows.push(formatSession('🛞 Qualifying', attrs.qualifying_start, index++));
rows.push(formatSession('🏁 Race', attrs.race_start, index++));
return rows.filter(Boolean).join('');
]]]
- type: map
aspect_ratio: 16x9
default_zoom: 10
entities:
- entity: sensor.f1_circuit_locatie
latitude: "{{ state_attr('sensor.f1_circuit_locatie', 'latitude') }}"
longitude: "{{ state_attr('sensor.f1_circuit_locatie', 'longitude') }}"
label_mode: icon
style:
ha-card:
background: linear-gradient(135deg, rgba(30,30,30,0.6), rgba(0,0,0,0.8));
border: none
box-shadow: none
border-radius: 0
- type: custom:button-card
entity: sensor.f12025_weather
show_name: false
show_state: false
show_icon: false
layout: custom
styles:
grid:
- grid-template-areas: |
"header"
"current_weather"
"race_weather"
- row-gap: 12px
card:
- padding: 12px
- border-radius: 8px
- background: linear-gradient(135deg,
- color: white
- font-family: sans-serif
- box-shadow: 0px 4px 20px rgba(0,0,0,0.2)
custom_fields:
header:
- font-size: 17px
- font-weight: 600
- text-align: center
- padding-bottom: 6px
- border-bottom: 1px solid rgba(255,255,255,0.2)
current_weather:
- font-size: 14px
- line-height: 1.2
- display: flex
- flex-direction: column
- gap: 6px
race_weather:
- font-size: 14px
- line-height: 1.2
- display: flex
- flex-direction: column
- gap: 6px
custom_fields:
header: |
[[[
const race_name = states['sensor.f12025_next_race'].attributes.race_name;
const circuit_locality = states['sensor.f12025_next_race'].attributes.circuit_locality;
const circuit_country = states['sensor.f12025_next_race'].attributes.circuit_country;
if (race_name && circuit_locality && circuit_country) {
return `
<div style="display: flex; flex-direction: column; align-items: center; gap: 6px;">
<div style="display: flex; align-items: center; gap: 8px; font-size: 22px; font-weight: 700;">
🏁 ${race_name} Weather
</div>
<div style="width: 60%; height: 1px; background: rgba(255, 255, 255, 0.3);"></div>
<div style="font-size: 16px; font-weight: 400; opacity: 0.7;">
${circuit_locality} - ${circuit_country}
</div>
</div>
`;
} else {
return `<div style="text-align: center;">No data available</div>`;
}
]]]
current_weather: |
[[[
const temp = entity.attributes.current_temperature;
const humidity = entity.attributes.current_humidity;
const cloud_cover = entity.attributes.current_cloud_cover;
const precipitation = entity.attributes.current_precipitation;
const wind_speed = entity.attributes.current_wind_speed;
const wind_dir = entity.attributes.current_wind_direction;
function mpsToBeaufort(mps) {
if (mps <= 0.3) return 0;
else if (mps <= 1.5) return 1;
else if (mps <= 3.3) return 2;
else if (mps <= 5.4) return 3;
else if (mps <= 7.9) return 4;
else if (mps <= 10.7) return 5;
else if (mps <= 13.8) return 6;
else if (mps <= 17.1) return 7;
else if (mps <= 20.7) return 8;
else if (mps <= 24.4) return 9;
else if (mps <= 28.4) return 10;
else if (mps <= 32.6) return 11;
return 12;
}
const wind_beaufort = mpsToBeaufort(wind_speed);
const rowStyles = [
"background: rgba(255,255,255,0.04); padding: 10px 12px; border-radius: 8px;",
"background: rgba(255,255,255,0.08); padding: 10px 12px; border-radius: 8px;"
];
return `
<div style="display: flex; flex-direction: column; gap: 8px;">
<div style="font-weight: 700; font-size: 18px; text-align: center; margin-bottom: 6px;">Current Weather</div>
<div style="${rowStyles[0]} display: flex; justify-content: space-between; align-items: center;">
<div>🌡️ Temperature</div><div><b>${Math.round(temp)}°C</b></div>
</div>
<div style="${rowStyles[1]} display: flex; justify-content: space-between; align-items: center;">
<div>💧 Humidity</div><div><b>${humidity}%</b></div>
</div>
<div style="${rowStyles[0]} display: flex; justify-content: space-between; align-items: center;">
<div>☁️ Cloud cover</div><div><b>${cloud_cover}%</b></div>
</div>
<div style="${rowStyles[1]} display: flex; justify-content: space-between; align-items: center;">
<div>🌧️ Precipitation</div><div><b>${precipitation} mm</b></div>
</div>
<div style="${rowStyles[0]} display: flex; justify-content: space-between; align-items: center;">
<div>🌬️ Wind speed</div><div><b>${wind_beaufort} Bft</b></div>
</div>
<div style="${rowStyles[1]} display: flex; justify-content: space-between; align-items: center;">
<div>🧭 Wind direction</div><div><b>${wind_dir}</b></div>
</div>
</div>
`;
]]]
race_weather: |
[[[
const race_temp = entity.attributes.race_temperature;
const race_humidity = entity.attributes.race_humidity;
const race_cloud_cover = entity.attributes.race_cloud_cover;
const race_precipitation = entity.attributes.race_precipitation;
const race_wind_speed = entity.attributes.race_wind_speed;
const race_wind_dir = entity.attributes.race_wind_direction;
function mpsToBeaufort(mps) {
if (mps <= 0.3) return 0;
else if (mps <= 1.5) return 1;
else if (mps <= 3.3) return 2;
else if (mps <= 5.4) return 3;
else if (mps <= 7.9) return 4;
else if (mps <= 10.7) return 5;
else if (mps <= 13.8) return 6;
else if (mps <= 17.1) return 7;
else if (mps <= 20.7) return 8;
else if (mps <= 24.4) return 9;
else if (mps <= 28.4) return 10;
else if (mps <= 32.6) return 11;
return 12;
}
const race_wind_beaufort = mpsToBeaufort(race_wind_speed);
const rowStyles = [
"background: rgba(255,255,255,0.04); padding: 10px 12px; border-radius: 8px;",
"background: rgba(255,255,255,0.08); padding: 10px 12px; border-radius: 8px;"
];
return `
<div style="display: flex; flex-direction: column; gap: 8px;">
<div style="font-weight: 700; font-size: 18px; text-align: center; margin-bottom: 6px;">Race Weekend Weather</div>
<div style="${rowStyles[0]} display: flex; justify-content: space-between; align-items: center;">
<div>🌡️ Temperature</div><div><b>${Math.round(race_temp)}°C</b></div>
</div>
<div style="${rowStyles[1]} display: flex; justify-content: space-between; align-items: center;">
<div>💧 Humidity</div><div><b>${race_humidity}%</b></div>
</div>
<div style="${rowStyles[0]} display: flex; justify-content: space-between; align-items: center;">
<div>☁️ Cloud cover</div><div><b>${race_cloud_cover}%</b></div>
</div>
<div style="${rowStyles[1]} display: flex; justify-content: space-between; align-items: center;">
<div>🌧️ Precipitation</div><div><b>${race_precipitation} mm</b></div>
</div>
<div style="${rowStyles[0]} display: flex; justify-content: space-between; align-items: center;">
<div>🌬️ Wind speed</div><div><b>${race_wind_beaufort} Bft</b></div>
</div>
<div style="${rowStyles[1]} display: flex; justify-content: space-between; align-items: center;">
<div>🧭 Wind direction</div><div><b>${race_wind_dir}</b></div>
</div>
</div>
`;
]]]
This template creates an extra sensor in Home Assistant that is used to show the Formula 1 circuit on the map
template:
- sensor:
- name: "F1 Circuit Locatie"
unique_id: 08728eb3-61d5-4cec-914b-4ba5d9e74648
state: "{{ state_attr('sensor.f12025_next_race', 'circuit_name') }}"
attributes:
latitude: "{{ state_attr('sensor.f12025_next_race', 'circuit_lat') }}"
longitude: "{{ state_attr('sensor.f12025_next_race', 'circuit_long') }}"
country: "{{ state_attr('sensor.f12025_next_race', 'circuit_country') }}"
icon: mdi:map-marker
Here the code for the Driver Standings
Make a “input_boolean.f1_race_results” so you can expand the card when you click on it.
type: custom:button-card
entity: sensor.f12025_driver_standings
icon: mdi:trophy-variant-outline
show_name: false
show_state: false
show_icon: false
layout: custom
tap_action:
action: call-service
service: input_boolean.toggle
service_data:
entity_id: input_boolean.f1_drivers_toggle
styles:
grid:
- grid-template-areas: |
"header"
"standings"
- row-gap: 12px
card:
- padding: 12px
- border-radius: 8px
- background: linear-gradient(135deg,
- color: white
- font-family: sans-serif
- box-shadow: 0px 4px 10px rgba(0,0,0,0.3)
custom_fields:
header:
- padding-bottom: 6px
- border-bottom: 1px solid rgba(255,255,255,0.2)
standings:
- font-size: 14px
- line-height: 1.6
- display: flex
- flex-direction: column
- gap: 6px
custom_fields:
header: |
[[[
if (!entity?.attributes) return "Standings not available";
const expanded = states['input_boolean.f1_drivers_toggle']?.state === 'on';
const icon = expanded ? "mdi:chevron-up" : "mdi:chevron-down";
return `
<div style="width: 100%; text-align: center;">
<div style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer;">
<span style="font-weight: 600; font-size: 17px;">
🏁 ${entity.attributes.season} Driver Standings - Round ${entity.attributes.round}
</span>
<ha-icon icon="${icon}" style="--mdc-icon-size: 20px;"></ha-icon>
</div>
</div>
`;
]]]
standings: |
[[[
if (!entity?.attributes?.driver_standings) return "No data";
const expanded = states['input_boolean.f1_drivers_toggle']?.state === 'on';
const standings = expanded
? entity.attributes.driver_standings
: entity.attributes.driver_standings.slice(0, 10);
const teamColors = {
"Red Bull": "#1E41FF",
"Ferrari": "#DC0000",
"Mercedes": "#00D2BE",
"McLaren": "#FF8700",
"Aston Martin": "#006F62",
"Alpine F1 Team": "#0090FF",
"RB F1 Team": "#6699FF",
"Haas F1 Team": "#B6BABD",
"Williams": "#005AFF",
"Sauber": "#52E252"
};
return standings.map((d, idx) => {
const code = d.Driver.code;
const name = `${d.Driver.givenName} ${d.Driver.familyName}`;
const team = d.Constructors[0]?.name || '';
const color = teamColors[team] || "#888";
const points = d.points;
const highlight = idx === 0
? '<b style="color: gold">' + name + '</b>'
: name;
const bg = idx % 2 === 0 ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.08)';
return `
<div style="display: flex; align-items: center; background: ${bg}; padding: 6px 10px; border-radius: 4px;">
<div style="width: 4px; height: 26px; background: ${color}; border-radius: 2px; margin-right: 10px;"></div>
<div style="flex: 1">
<div style="color: var(--primary-text-color); font-weight: 500;">
<strong>${d.position}.</strong> <strong>${code}</strong> - ${highlight}
</div>
<div style="font-size: 12px; color: var(--secondary-text-color);">${team} • ${points} pt${points == 1 ? '' : 's'}</div>
</div>
</div>`;
}).join('');
]]]
Here the code for the Team Standings.
type: custom:button-card
entity: sensor.f12025_constructor_standings
icon: mdi:car-cog
show_name: false
show_state: false
show_icon: false
layout: custom
styles:
grid:
- grid-template-areas: |
"header"
"standings"
- row-gap: 12px
card:
- padding: 12px
- border-radius: 8px
- background: linear-gradient(135deg,
- color: white
- font-family: sans-serif
- box-shadow: 0px 4px 10px rgba(0,0,0,0.3)
custom_fields:
header:
- font-size: 17px
- font-weight: 600
- text-align: center
- padding-bottom: 6px
- border-bottom: 1px solid rgba(255,255,255,0.2)
standings:
- font-size: 14px
- line-height: 1.6
- display: flex
- flex-direction: column
- gap: 6px
custom_fields:
header: |
[[[
if (!entity?.attributes) return "Standings not available";
return "🏁 2025 Team Standings - Round " + entity.attributes.round;
]]]
standings: |
[[[
if (!entity?.attributes?.constructor_standings) return "No data";
const teamColors = {
"Red Bull": "#1E41FF",
"Ferrari": "#DC0000",
"Mercedes": "#00D2BE",
"McLaren": "#FF8700",
"Aston Martin": "#006F62",
"Alpine F1 Team": "#0090FF",
"RB F1 Team": "#6699FF",
"Haas F1 Team": "#B6BABD",
"Williams": "#005AFF",
"Sauber": "#52E252"
};
return entity.attributes.constructor_standings.slice(0, 10).map((c, idx) => {
const name = c.Constructor.name;
const nationality = c.Constructor.nationality;
const points = c.points;
const wins = c.wins;
const color = teamColors[name] || "#888";
const position = c.position;
const highlight = idx === 0
? '<b style="color: gold">' + name + '</b>'
: name;
const bg = idx % 2 === 0 ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.08)';
return `
<div style="display: flex; align-items: center; background: ${bg}; padding: 6px 10px; border-radius: 4px;">
<div style="width: 4px; height: 26px; background: ${color}; border-radius: 2px; margin-right: 10px;"></div>
<div style="flex: 1">
<div style="color: var(--primary-text-color); font-weight: 500;"><strong>${position}.</strong> ${highlight}</div>
<div style="font-size: 12px; color: var(--secondary-text-color);">${nationality} • ${points} pt${points == 1 ? '' : 's'} • ${wins} win${wins == 1 ? '' : 's'}</div>
</div>
</div>`;
}).join('');
]]]