diff --git a/served.html b/served.html
index b5bd1ba..00a5f75 100644
--- a/served.html
+++ b/served.html
@@ -2509,79 +2509,42 @@
Understanding the Graph Behavior
const declination = 23.45 * Math.sin((360/365) * (dayOfYear - 81) * Math.PI / 180);
const declinationRad = declination * Math.PI / 180;
- // Draw gray line as a band (civil twilight: solar altitude between -6° and +6°)
- const grayLinePoints = [];
-
- // Sweep across all longitudes and latitudes to find twilight zone
- for (let lon = -180; lon <= 180; lon += 2) {
- for (let lat = -90; lat <= 90; lat += 2) {
- const latRad = lat * Math.PI / 180;
-
- // Calculate hour angle from longitude and time
- const solarLon = -15 * (hours - 12); // Sun's longitude at this time
- const hourAngle = (lon - solarLon) * Math.PI / 180;
-
- // Calculate solar altitude using spherical astronomy
- const sinAlt = Math.sin(latRad) * Math.sin(declinationRad) +
- Math.cos(latRad) * Math.cos(declinationRad) * Math.cos(hourAngle);
- const solarAlt = Math.asin(Math.max(-1, Math.min(1, sinAlt))) * 180 / Math.PI;
-
- // Gray line is where solar altitude is near 0 (±6 degrees for civil twilight)
- if (Math.abs(solarAlt) < 6) {
- grayLinePoints.push([lat, lon]);
- }
- }
+ // Draw gray line as a band (civil twilight: solar altitude between -6° and +6°).
+ // Solve sin(α) = sinDecl·sin(lat) + cosDecl·cos(HA)·cos(lat)
+ // = R·sin(lat + φ)
+ // for α = ±6° at every longitude, where
+ // R = √(sin²δ + cos²δ·cos²HA), φ = atan2(cosδ·cosHA, sinδ).
+ // Then lat = asin(sin(α)/R) − φ, clamped to [−90°, 90°].
+ const sinDecl = Math.sin(declinationRad);
+ const cosDecl = Math.cos(declinationRad);
+ const solarLon = -15 * (hours - 12);
+ const sinAltDay = Math.sin(6 * Math.PI / 180);
+ const sinAltNight = Math.sin(-6 * Math.PI / 180);
+
+ const dayEdge = [];
+ const nightEdge = [];
+ for (let lon = -180; lon <= 180; lon += 1) {
+ const hourAngle = (lon - solarLon) * Math.PI / 180;
+ const cosHA = Math.cos(hourAngle);
+ const R = Math.sqrt(sinDecl * sinDecl + cosDecl * cosDecl * cosHA * cosHA);
+ const phi = Math.atan2(cosDecl * cosHA, sinDecl);
+ const argDay = Math.max(-1, Math.min(1, sinAltDay / R));
+ const argNight = Math.max(-1, Math.min(1, sinAltNight / R));
+ const latDay = (Math.asin(argDay) - phi) * 180 / Math.PI;
+ const latNight = (Math.asin(argNight) - phi) * 180 / Math.PI;
+ dayEdge.push([Math.max(-90, Math.min(90, latDay)), lon]);
+ nightEdge.push([Math.max(-90, Math.min(90, latNight)), lon]);
}
- if (grayLinePoints.length > 0) {
- // Create a feature group for better visualization
- const features = [];
-
- // Group nearby points to create a continuous band
- const grouped = {};
- grayLinePoints.forEach(([lat, lon]) => {
- const latKey = Math.round(lat / 5) * 5; // Group by 5-degree latitude bands
- if (!grouped[latKey]) grouped[latKey] = [];
- grouped[latKey].push(lon);
- });
-
- // For each latitude band, create a polygon strip
- const polygons = [];
- for (const [latKey, lons] of Object.entries(grouped)) {
- const lat = parseFloat(latKey);
- lons.sort((a, b) => a - b);
-
- // Create rectangles for continuous longitude segments
- let segmentStart = lons[0];
- for (let i = 1; i < lons.length; i++) {
- if (lons[i] - lons[i-1] > 10) { // Gap detected
- // Close current segment
- polygons.push([
- [lat - 5, segmentStart],
- [lat + 5, segmentStart],
- [lat + 5, lons[i-1]],
- [lat - 5, lons[i-1]]
- ]);
- segmentStart = lons[i];
- }
- }
- // Close final segment
- polygons.push([
- [lat - 5, segmentStart],
- [lat + 5, segmentStart],
- [lat + 5, lons[lons.length - 1]],
- [lat - 5, lons[lons.length - 1]]
- ]);
- }
-
- grayLineLayer = L.polygon(polygons, {
- color: '#fbbf24',
- fillColor: '#fbbf24',
- weight: 1,
- opacity: 0.5,
- fillOpacity: 0.3
- }).addTo(map);
- }
+ // Closed polygon: day edge west→east, then night edge east→west.
+ const polygon = dayEdge.concat(nightEdge.reverse());
+ grayLineLayer = L.polygon(polygon, {
+ color: '#fbbf24',
+ fillColor: '#fbbf24',
+ weight: 1,
+ opacity: 0.5,
+ fillOpacity: 0.3
+ }).addTo(map);
// Update time display
if (grayLineHour !== null) {
diff --git a/src/dvoacap/dashboard/dashboard.html b/src/dvoacap/dashboard/dashboard.html
index b5bd1ba..00a5f75 100644
--- a/src/dvoacap/dashboard/dashboard.html
+++ b/src/dvoacap/dashboard/dashboard.html
@@ -2509,79 +2509,42 @@ Understanding the Graph Behavior
const declination = 23.45 * Math.sin((360/365) * (dayOfYear - 81) * Math.PI / 180);
const declinationRad = declination * Math.PI / 180;
- // Draw gray line as a band (civil twilight: solar altitude between -6° and +6°)
- const grayLinePoints = [];
-
- // Sweep across all longitudes and latitudes to find twilight zone
- for (let lon = -180; lon <= 180; lon += 2) {
- for (let lat = -90; lat <= 90; lat += 2) {
- const latRad = lat * Math.PI / 180;
-
- // Calculate hour angle from longitude and time
- const solarLon = -15 * (hours - 12); // Sun's longitude at this time
- const hourAngle = (lon - solarLon) * Math.PI / 180;
-
- // Calculate solar altitude using spherical astronomy
- const sinAlt = Math.sin(latRad) * Math.sin(declinationRad) +
- Math.cos(latRad) * Math.cos(declinationRad) * Math.cos(hourAngle);
- const solarAlt = Math.asin(Math.max(-1, Math.min(1, sinAlt))) * 180 / Math.PI;
-
- // Gray line is where solar altitude is near 0 (±6 degrees for civil twilight)
- if (Math.abs(solarAlt) < 6) {
- grayLinePoints.push([lat, lon]);
- }
- }
+ // Draw gray line as a band (civil twilight: solar altitude between -6° and +6°).
+ // Solve sin(α) = sinDecl·sin(lat) + cosDecl·cos(HA)·cos(lat)
+ // = R·sin(lat + φ)
+ // for α = ±6° at every longitude, where
+ // R = √(sin²δ + cos²δ·cos²HA), φ = atan2(cosδ·cosHA, sinδ).
+ // Then lat = asin(sin(α)/R) − φ, clamped to [−90°, 90°].
+ const sinDecl = Math.sin(declinationRad);
+ const cosDecl = Math.cos(declinationRad);
+ const solarLon = -15 * (hours - 12);
+ const sinAltDay = Math.sin(6 * Math.PI / 180);
+ const sinAltNight = Math.sin(-6 * Math.PI / 180);
+
+ const dayEdge = [];
+ const nightEdge = [];
+ for (let lon = -180; lon <= 180; lon += 1) {
+ const hourAngle = (lon - solarLon) * Math.PI / 180;
+ const cosHA = Math.cos(hourAngle);
+ const R = Math.sqrt(sinDecl * sinDecl + cosDecl * cosDecl * cosHA * cosHA);
+ const phi = Math.atan2(cosDecl * cosHA, sinDecl);
+ const argDay = Math.max(-1, Math.min(1, sinAltDay / R));
+ const argNight = Math.max(-1, Math.min(1, sinAltNight / R));
+ const latDay = (Math.asin(argDay) - phi) * 180 / Math.PI;
+ const latNight = (Math.asin(argNight) - phi) * 180 / Math.PI;
+ dayEdge.push([Math.max(-90, Math.min(90, latDay)), lon]);
+ nightEdge.push([Math.max(-90, Math.min(90, latNight)), lon]);
}
- if (grayLinePoints.length > 0) {
- // Create a feature group for better visualization
- const features = [];
-
- // Group nearby points to create a continuous band
- const grouped = {};
- grayLinePoints.forEach(([lat, lon]) => {
- const latKey = Math.round(lat / 5) * 5; // Group by 5-degree latitude bands
- if (!grouped[latKey]) grouped[latKey] = [];
- grouped[latKey].push(lon);
- });
-
- // For each latitude band, create a polygon strip
- const polygons = [];
- for (const [latKey, lons] of Object.entries(grouped)) {
- const lat = parseFloat(latKey);
- lons.sort((a, b) => a - b);
-
- // Create rectangles for continuous longitude segments
- let segmentStart = lons[0];
- for (let i = 1; i < lons.length; i++) {
- if (lons[i] - lons[i-1] > 10) { // Gap detected
- // Close current segment
- polygons.push([
- [lat - 5, segmentStart],
- [lat + 5, segmentStart],
- [lat + 5, lons[i-1]],
- [lat - 5, lons[i-1]]
- ]);
- segmentStart = lons[i];
- }
- }
- // Close final segment
- polygons.push([
- [lat - 5, segmentStart],
- [lat + 5, segmentStart],
- [lat + 5, lons[lons.length - 1]],
- [lat - 5, lons[lons.length - 1]]
- ]);
- }
-
- grayLineLayer = L.polygon(polygons, {
- color: '#fbbf24',
- fillColor: '#fbbf24',
- weight: 1,
- opacity: 0.5,
- fillOpacity: 0.3
- }).addTo(map);
- }
+ // Closed polygon: day edge west→east, then night edge east→west.
+ const polygon = dayEdge.concat(nightEdge.reverse());
+ grayLineLayer = L.polygon(polygon, {
+ color: '#fbbf24',
+ fillColor: '#fbbf24',
+ weight: 1,
+ opacity: 0.5,
+ fillOpacity: 0.3
+ }).addTo(map);
// Update time display
if (grayLineHour !== null) {