{"id":178,"date":"2026-09-25T18:24:51","date_gmt":"2026-09-25T16:24:51","guid":{"rendered":"https:\/\/helvetipix.ch\/?page_id=178"},"modified":"2026-09-25T18:24:51","modified_gmt":"2026-09-25T16:24:51","slug":"astro-photo","status":"publish","type":"page","link":"https:\/\/helvetipix.ch\/?page_id=178","title":{"rendered":"Astro photo"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n<title>Nuit \u00c9toil\u00e9e &#8211; Helvetipix<\/title>\n<style>\n* { box-sizing: border-box; margin: 0; padding: 0; }\nbody {\nfont-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\nbackground-color: #0a0a0f;\ncolor: #c5c6c7;\ndisplay: flex;\njustify-content: center;\nalign-items: flex-start;\nmin-height: 100vh;\npadding: 16px;\n-webkit-text-size-adjust: 100%;\n}\n.card {\nbackground-color: #151520;\nborder-radius: 16px;\npadding: 20px;\nmax-width: 520px;\nwidth: 100%;\nbox-shadow: 0 8px 32px rgba(0,0,0,0.6);\nborder: 1px solid #2a2a3a;\n}\nh2 {\ncolor: #7dd3fc;\ntext-align: center;\nmargin-bottom: 16px;\nfont-size: 1.4em;\n}\n.section {\nmargin-bottom: 16px;\npadding-bottom: 14px;\nborder-bottom: 1px solid #2a2a3a;\n}\n.section:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }\n.label {\nfont-size: 0.75em;\ncolor: #7dd3fc;\ntext-transform: uppercase;\nletter-spacing: 1.5px;\nmargin-bottom: 6px;\n}\n.value { font-size: 1em; color: #e0e0e0; margin-bottom: 4px; }\n.score-box {\ndisplay: flex;\nalign-items: center;\njustify-content: space-between;\npadding: 10px 14px;\nborder-radius: 10px;\nmargin-top: 8px;\nfont-weight: bold;\n}\n.score-value { font-size: 1.6em; }\n.suggestion {\nbackground: rgba(125, 211, 252, 0.08);\nborder-left: 3px solid #7dd3fc;\npadding: 12px;\nborder-radius: 6px;\nmargin-top: 8px;\nline-height: 1.5;\n}\n.film { font-style: italic; color: #fbbf24; }\n.loading { color: #7dd3fc; font-style: italic; text-align: center; padding: 20px; }\n.error { color: #f87171; font-size: 0.9em; }\n.badge {\ndisplay: inline-block;\npadding: 3px 8px;\nborder-radius: 4px;\nfont-size: 0.75em;\nfont-weight: bold;\nmargin-left: 6px;\n}\n@media (max-width: 380px) {\n.card { padding: 14px; }\nh2 { font-size: 1.2em; }\n.score-value { font-size: 1.3em; }\n}\n@media (min-width: 768px) {\nbody { padding: 40px 20px; }\n.card { padding: 28px; max-width: 560px; }\n}\n<\/style>\n<\/head>\n<body>\n\n<div class=\"card\" id=\"app\">\n<h2>\ud83c\udf19 Nuit \u00c9toil\u00e9e<\/h2>\n<div id=\"content\">\n<p class=\"loading\">Analyse des conditions&#8230;<\/p>\n<\/div>\n<\/div>\n\n<script>\n\/\/ ============================================\n\/\/ CONFIGURATION\n\/\/ ============================================\n\n\/\/ Spots d'observation en Suisse (utilis\u00e9s si mauvaises conditions)\nconst SPOTS_SUISSE = [\n{ nom: \"Parc Naturel de Gantrisch\", canton: \"BE\", lat: 46.75, lon: 7.45, note: \"1er Dark Sky Park de Suisse\" },\n{ nom: \"Vall\u00e9e de Joux\", canton: \"VD\", lat: 46.64, lon: 6.28, note: \"Ciel pr\u00e9serv\u00e9 du Jura\" },\n{ nom: \"Col du Pillon\", canton: \"VD\", lat: 46.35, lon: 7.21, note: \"Altitude, horizon d\u00e9gag\u00e9\" },\n{ nom: \"Amden\", canton: \"SG\", lat: 47.14, lon: 9.18, note: \"Ciel alpin, au-dessus du brouillard\" },\n{ nom: \"Naturpark Diemtigtal\", canton: \"BE\", lat: 46.60, lon: 7.50, note: \"Vall\u00e9e recul\u00e9e\" },\n{ nom: \"Lac de Neuch\u00e2tel (rive nord)\", canton: \"NE\", lat: 46.95, lon: 6.85, note: \"Horizon ouest d\u00e9gag\u00e9\" }\n ];\n\n\/\/ Films pour soir\u00e9es nuageuses\nconst FILMS = [\n\"Blade Runner (1982)\",\n\"Interstellar (2014)\",\n\"Contact (1997)\",\n\"Gravity (2013)\",\n\"The Martian (2015)\",\n\"Dune (2021)\",\n\"Le Cinqui\u00e8me \u00c9l\u00e9ment (1997)\",\n\"Arrival (2016)\",\n\"Ad Astra (2019)\"\n ];\n\n\/\/ Villes suisses pour estimation de pollution lumineuse (rayon approx. 15km)\nconst VILLES = [\n{ nom: \"Zurich\", lat: 47.37, lon: 8.54, pop: 430000 },\n{ nom: \"Gen\u00e8ve\", lat: 46.20, lon: 6.14, pop: 200000 },\n{ nom: \"B\u00e2le\", lat: 47.56, lon: 7.59, pop: 175000 },\n{ nom: \"Lausanne\", lat: 46.52, lon: 6.63, pop: 140000 },\n{ nom: \"Berne\", lat: 46.95, lon: 7.45, pop: 135000 },\n{ nom: \"Lucerne\", lat: 47.05, lon: 8.31, pop: 82000 },\n{ nom: \"Saint-Gall\", lat: 47.42, lon: 9.38, pop: 76000 },\n{ nom: \"Lugano\", lat: 46.00, lon: 8.95, pop: 63000 },\n{ nom: \"Bienne\", lat: 47.14, lon: 7.25, pop: 55000 },\n{ nom: \"Neuch\u00e2tel\", lat: 46.99, lon: 6.93, pop: 34000 }\n ];\n\n\/\/ ============================================\n\/\/ CODE\n\/\/ ============================================\n\nconst appContent = document.getElementById('content');\n\n\/\/ --- Distance Haversine (km) ---\nfunction distanceKm(lat1, lon1, lat2, lon2) {\nconst R = 6371;\nconst dLat = (lat2 - lat1) * Math.PI \/ 180;\nconst dLon = (lon2 - lon1) * Math.PI \/ 180;\nconst a = Math.sin(dLat\/2) * Math.sin(dLat\/2) +\nMath.cos(lat1 * Math.PI \/ 180) * Math.cos(lat2 * Math.PI \/ 180) *\nMath.sin(dLon\/2) * Math.sin(dLon\/2);\nreturn R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));\n}\n\n\/\/ --- Estimation pollution lumineuse (0-10, 10 = ciel pur) ---\nfunction estimerPollutionLumineuse(lat, lon) {\nlet pollutionMax = 0; \/\/ 0 = aucune pollution\nfor (const ville of VILLES) {\nconst dist = distanceKm(lat, lon, ville.lat, ville.lon);\n\/\/ Rayon d'influence : plus la ville est grande, plus loin elle \u00e9claire\nconst rayon = 10 + (ville.pop \/ 100000) * 15; \/\/ 10 \u00e0 ~70 km\nif (dist < rayon) {\n\/\/ Intensit\u00e9 inverse de la distance\nconst intensite = 10 * (1 - dist \/ rayon) * (0.5 + ville.pop \/ 200000);\npollutionMax = Math.max(pollutionMax, intensite);\n}\n}\n\/\/ Score : 10 = pas de pollution, 0 = tr\u00e8s pollu\u00e9\nreturn Math.round(Math.max(0, 10 - pollutionMax) * 10) \/ 10;\n}\n\n\/\/ --- Score m\u00e9t\u00e9o (0-10) ---\nfunction scoreMeteo(cloudCover, precipitation, visibility) {\nlet score = 10;\n\/\/ Nuages : impact majeur\nscore -= (cloudCover \/ 100) * 6;\n\/\/ Pluie : r\u00e9dhibitoire\nif (precipitation > 0.5) score -= 3;\nelse if (precipitation > 0.1) score -= 1.5;\n\/\/ Visibilit\u00e9 : impact mod\u00e9r\u00e9\nif (visibility !== null && visibility !== undefined) {\nconst visKm = visibility \/ 1000;\nif (visKm < 5) score -= 2;\nelse if (visKm < 10) score -= 1;\n}\nreturn Math.max(0, Math.min(10, Math.round(score * 10) \/ 10));\n}\n\n\/\/ --- Score global astro (0-10) ---\nfunction scoreAstroGlobal(meteo, pollution) {\n\/\/ M\u00e9t\u00e9o = 60% du score, pollution = 40%\nreturn Math.round((meteo * 0.6 + pollution * 0.4) * 10) \/ 10;\n}\n\n\/\/ --- R\u00e9cup\u00e9rer m\u00e9t\u00e9o ---\nasync function fetchWeather(lat, lon) {\nconst url = https:\/\/api.open-meteo.com\/v1\/forecast?latitude=${lat}&#038;longitude=${lon}&#038;current=cloud_cover,precipitation,visibility&#038;timezone=auto;\ntry {\nconst response = await fetch(url);\nconst data = await response.json();\nif (data.error) throw new Error(data.reason);\nreturn {\ncloudCover: data.current.cloud_cover,\nprecipitation: data.current.precipitation || 0,\nvisibility: data.current.visibility\n};\n} catch (error) {\nconsole.error(\"Erreur m\u00e9t\u00e9o:\", error);\nreturn null;\n}\n}\n\n\/\/ --- Suggestion spot Suisse ---\nfunction suggererSpot() {\nconst spot = SPOTS_SUISSE[Math.floor(Math.random() * SPOTS_SUISSE.length)];\nreturn spot;\n}\n\n\/\/ --- Suggestion film ---\nfunction suggererFilm() {\nreturn FILMS[Math.floor(Math.random() * FILMS.length)];\n}\n\n\/\/ --- Couleur selon score ---\nfunction couleurScore(score) {\nif (score >= 8) return '#4ade80'; \/\/ vert\nif (score >= 6) return '#a3e635'; \/\/ vert clair\nif (score >= 4) return '#fbbf24'; \/\/ jaune\nif (score >= 2) return '#fb923c'; \/\/ orange\nreturn '#f87171'; \/\/ rouge\n}\n\n\/\/ --- Affichage ---\nfunction afficher(meteo, pollution, astro, lat, lon) {\nconst spot = suggererSpot();\nconst film = suggererFilm();\nconst couleur = couleurScore(astro);\n\nlet html = '';\n\n\/\/ Ma position\nhtml += <div class=\"section\">;\nhtml += <div class=\"label\"&gt;\ud83d\udccd Ma position&lt;\/div>;\nif (!meteo) {\nhtml += <p class=\"error\"&gt;M\u00e9t\u00e9o indisponible.&lt;\/p>;\n} else {\nhtml += <div class=\"value\"&gt;Nuages : ${meteo.cloudCover}% | Pluie : ${meteo.precipitation} mm&lt;\/div>;\nhtml += <div class=\"value\"&gt;Pollution lumineuse estim\u00e9e : ${pollution}\/10<\/div>; html +=\n; html +=<span>Score Astro<\/span>; html +=<span class=\"score-value\" style=\"color: ${couleur};\"&gt;${astro}\/10<\/span>; html +=\n; } html +=`;\n\n\/\/ Recommandation\nhtml += <div class=\"section\">;\nif (astro >= 6) {\nhtml += <div class=\"label\"&gt;\u2705 Conditions favorables&lt;\/div>;\nhtml += <div class=\"suggestion\"&gt;Bonne soir\u00e9e pour sortir le mat\u00e9riel ! Le ciel devrait \u00eatre suffisamment d\u00e9gag\u00e9.&lt;\/div>;\n} else if (astro >= 4) {\nhtml += <div class=\"label\"&gt;\u26a0\ufe0f Conditions moyennes&lt;\/div>;\nhtml += <div class=\"suggestion\"&gt;Conditions incertaines. Vous pouvez tenter, mais pr\u00e9voyez une solution de repli.&lt;\/div>;\n} else {\nhtml += <div class=\"label\"&gt;\u274c Conditions d\u00e9favorables&lt;\/div>;\nhtml += <div class=\"suggestion\"&gt;Ciel couvert ou pollution lumineuse trop forte. Voici deux alternatives :&lt;\/div>;\nhtml += <div style=\"margin-top: 12px; padding: 10px; background: rgba(74, 222, 128, 0.08); border-radius: 8px;\">;\nhtml += <strong&gt;\ud83c\udf1f Spot en Suisse :&lt;\/strong&gt;${spot.nom} (latex\n{spot.canton})&lt;br>`; html += `<small style=\"color: #9ca3af;\"&gt;\n\n{spot.note}<\/small>; html +=; html +=\n; html +=<strong>\ud83c\udf7f Ou alors :<\/strong> <span class=\"film\">${film}<\/span>; html +=\n; } html +=`;\n\nappContent.innerHTML = html;\n}\n\n\/\/ --- Initialisation ---\nasync function init() {\n\/\/ Coordonn\u00e9es par d\u00e9faut (centre Suisse) si g\u00e9oloc refus\u00e9e\nlet lat = 46.8182;\nlet lon = 8.2275;\nlet geoOk = false;\n\n\/\/ Tentative de g\u00e9olocalisation\nif (navigator.geolocation) {\ntry {\nconst position = await new Promise((resolve, reject) => {\nnavigator.geolocation.getCurrentPosition(resolve, reject, {\nenableHighAccuracy: true,\ntimeout: 8000,\nmaximumAge: 300000\n});\n});\nlat = position.coords.latitude;\nlon = position.coords.longitude;\ngeoOk = true;\n} catch (e) {\nconsole.warn(\"G\u00e9olocalisation indisponible, utilisation du centre Suisse.\");\n}\n}\n\n\/\/ Calculs\nconst meteo = await fetchWeather(lat, lon);\nconst pollution = estimerPollutionLumineuse(lat, lon);\n\nlet astro = 0;\nif (meteo) {\nconst scoreM = scoreMeteo(meteo.cloudCover, meteo.precipitation, meteo.visibility);\nastro = scoreAstroGlobal(scoreM, pollution);\n}\n\nafficher(meteo, pollution, astro, lat, lon);\n}\n\ninit();\n<\/script>\n\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Nuit \u00c9toil\u00e9e &#8211; Helvetipix \ud83c\udf19 Nuit \u00c9toil\u00e9e Analyse des conditions&#8230;<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-178","page","type-page","status-publish","hentry"],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/helvetipix.ch\/index.php?rest_route=\/wp\/v2\/pages\/178","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/helvetipix.ch\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/helvetipix.ch\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/helvetipix.ch\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/helvetipix.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=178"}],"version-history":[{"count":2,"href":"https:\/\/helvetipix.ch\/index.php?rest_route=\/wp\/v2\/pages\/178\/revisions"}],"predecessor-version":[{"id":182,"href":"https:\/\/helvetipix.ch\/index.php?rest_route=\/wp\/v2\/pages\/178\/revisions\/182"}],"wp:attachment":[{"href":"https:\/\/helvetipix.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=178"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}