Millist tekki vajab sinu hobune täna öösel?

3link rel="stylesheet" href="https://nuvallo.de/apps/blog/assets/styles.css"3 3div class="decken-page"3 3style3 /* Page-specific styles */ .decken-page { background: var(--green-light); min-height: 100vh; } .decken-page main { max-width: 520px; padding: 1.5rem 1rem; margin: 0 auto; } .decken-page h1 { font-size: 1.6rem; margin-bottom: 0.5rem; text-align: center; } .decken-subtitle { text-align: center; color: var(--gray); margin-bottom: 2rem; font-size: 0.95rem; } /* Horse type selector */ .horse-types { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.75rem; margin-bottom: 1.5rem; } .horse-type-btn { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; padding: 1rem 0.5rem; border: 2px solid var(--gray-light); border-radius: var(--radius-md); background: var(--white); cursor: pointer; transition: all 0.2s; font-family: var(--font-body); font-size: 0.85rem; font-weight: 500; color: var(--black); } .decken-page .horse-type-btn:hover { border-color: var(--green-dark); } .decken-page .horse-type-btn.active, .decken-page button.horse-type-btn.active { border-color: var(--green-dark) !important; background: var(--green-dark) !important; color: var(--white) !important; box-shadow: 0 0 0 3px rgba(74, 144, 110, 0.35); } .decken-page .horse-type-btn.active .horse-type-label, .decken-page .horse-type-btn.active .horse-type-icon { color: var(--white) !important; } .horse-type-icon { font-size: 2.2rem; } .horse-type-label { text-align: center; line-height: 1.2; } /* Postal code input */ .plz-row { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; } .plz-row input { flex: 1; padding: 0.85rem 1rem; border: 2px solid var(--gray-light); border-radius: var(--radius-md); font-size: 1.1rem; font-family: var(--font-body); text-align: center; letter-spacing: 2px; transition: border-color 0.2s; } .plz-row input:focus { outline: none; border-color: var(--green-dark); } .go-btn { padding: 0.85rem 2rem; border: none; border-radius: var(--radius-md); background: var(--green-dark); color: var(--white); font-family: var(--font-heading); font-weight: 600; font-size: 1.1rem; cursor: pointer; transition: background 0.2s; white-space: nowrap; } .go-btn:hover { background: #3d7e5e; } .go-btn:disabled { opacity: 0.5; cursor: default; } /* Loading */ .loading { text-align: center; padding: 2rem 0; display: none; } .loading.visible { display: block; } .spinner { display: inline-block; width: 36px; height: 36px; border: 3px solid var(--gray-light); border-top-color: var(--green-dark); border-radius: 50%; animation: spin 0.7s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .loading p { margin-top: 0.75rem; color: var(--gray); font-size: 0.9rem; } /* Result card */ .result-card { display: none; animation: fadeUp 0.4s ease; } .result-card.visible { display: block; } @keyframes fadeUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } .recommendation-box { background: var(--white); border-radius: var(--radius-lg); padding: 1.5rem; text-align: center; margin-bottom: 1rem; box-shadow: 0 2px 12px rgba(0,0,0,0.06); } .rec-icon { font-size: 3.5rem; margin-bottom: 0.5rem; } .rec-title { font-family: var(--font-heading); font-size: 1.3rem; font-weight: 600; margin-bottom: 0.25rem; } .rec-detail { color: var(--gray); font-size: 0.95rem; line-height: 1.5; } .rec-fill { display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin: 0.75rem 0 0.25rem; } .rec-fill-label { font-size: 0.85rem; color: var(--gray); } .fill-bar { display: flex; gap: 3px; } .fill-dot { width: 18px; height: 18px; border-radius: 50%; background: var(--gray-light); transition: background 0.3s; } .fill-dot.filled { background: var(--green-dark); } /* Weather panel */ .weather-panel { background: var(--white); border-radius: var(--radius-lg); padding: 1.25rem; box-shadow: 0 2px 12px rgba(0,0,0,0.06); margin-bottom: 1rem; } .weather-panel h2 { font-size: 1rem; margin-bottom: 0.75rem; color: var(--black); text-align: center; } .weather-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; } .weather-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: var(--green-light); border-radius: var(--radius-sm); } .weather-item-icon { font-size: 1.5rem; flex-shrink: 0; } .weather-item-info { line-height: 1.3; } .weather-item-label { font-size: 0.75rem; color: var(--gray); } .weather-item-value { font-weight: 600; font-size: 0.95rem; } /* Embed / share section */ .embed-section { background: var(--white); border-radius: var(--radius-lg); padding: 1.25rem; box-shadow: 0 2px 12px rgba(0,0,0,0.06); text-align: center; margin-top: 1.5rem; } .embed-section h2 { font-size: 1rem; margin-bottom: 0.75rem; } .embed-code { width: 100%; padding: 0.6rem; border: 1px solid var(--gray-light); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.8rem; resize: none; color: var(--gray); } .copy-btn { margin-top: 0.5rem; padding: 0.5rem 1.5rem; border: 1px solid var(--green-dark); border-radius: var(--radius-sm); background: var(--white); color: var(--green-dark); font-weight: 600; cursor: pointer; font-family: var(--font-body); transition: all 0.2s; } .copy-btn:hover { background: var(--green-light); } /* Error */ .error-msg { text-align: center; color: #d32f2f; font-size: 0.9rem; padding: 0.75rem; display: none; } .error-msg.visible { display: block; } /* Retry */ .retry-row { text-align: center; margin-top: 1rem; } .retry-btn { padding: 0.6rem 1.5rem; border: 2px solid var(--green-dark); border-radius: var(--radius-md); background: var(--white); color: var(--green-dark); font-family: var(--font-heading); font-weight: 600; cursor: pointer; transition: all 0.2s; } .retry-btn:hover { background: var(--green-light); } 3/style3 3main3 3!-- Input Form --3 3section id="form-section"3 3!-- 3h13Welche Decke braucht dein Pferd heute Nacht?3/h13 --3 3p style="font-weight:500; margin-bottom:0.5rem; text-align:center;"3Vali hobuse tüüp:3/p3 3div class="horse-types"3 3button class="horse-type-btn" data-type="jung" onclick="selectHorse(this)"3 3span class="horse-type-icon"3🐎3/span3 3span class="horse-type-label"3Noor hobune3/span3 3/button3 3button class="horse-type-btn" data-type="alt" onclick="selectHorse(this)"3 3span class="horse-type-icon"3🐴3/span3 3span class="horse-type-label"3Vanem hobune3/span3 3/button3 3button class="horse-type-btn" data-type="ekzem" onclick="selectHorse(this)"3 3span class="horse-type-icon"3✨3/span3 3span class="horse-type-label"3Ekseemik / õhuke karv3/span3 3/button3 3/div3 3div class="plz-row"3 3input type="text" id="plz-input" placeholder="Sisesta postiindeks" maxlength="5" inputmode="numeric" pattern="[0-9]*"3 3button class="go-btn" id="go-btn" onclick="checkWeather()" disabled3Vaata!3/button3 3/div3 3/section3 3!-- Loading --3 3div class="loading" id="loading"3 3div class="spinner"33/div3 3p3Ilmateate laadimine…3/p3 3/div3 3!-- Error --3 3div class="error-msg" id="error-msg"33/div3 3!-- Result --3 3div class="result-card" id="result-card"3 3div class="recommendation-box"3 3div class="rec-icon" id="rec-icon"33/div3 3div class="rec-title" id="rec-title"33/div3 3div class="rec-fill"3 3span class="rec-fill-label"3Soojus:3/span3 3div class="fill-bar" id="fill-bar"33/div3 3/div3 3div class="rec-detail" id="rec-detail"33/div3 3/div3 3div class="weather-panel"3 3h23🌡️ Ilm täna öösel3/h23 3div class="weather-grid" id="weather-grid"33/div3 3/div3 3div class="retry-row"3 3button class="retry-btn" onclick="resetForm()"3↻ Kontrolli uuesti3/button3 3/div3 3/div3 3!-- Embed --3 3div class="embed-section"3 3h23🔗 Manusta oma veebisaidile3/h23 3textarea class="embed-code" rows="5" readonly id="embed-code"33/textarea3 3button class="copy-btn" onclick="copyEmbed()"3Kopeeri kood3/button3 3/div3 3/main3 3script3 (function() { var selectedHorse = null; var plzInput = document.getElementById('plz-input'); var goBtn = document.getElementById('go-btn'); // Set embed code — always point to nuvallo.de regardless of current host var nuvalloURL = 'https://nuvallo.de/pages/pferdedecken'; document.getElementById('embed-code').value = '3iframe src="' + nuvalloURL + '" width="100%" height="700" style="border:none;border-radius:12px;" loading="lazy"33/iframe3\n' + '3p style="text-align:center;font-size:0.85rem;margin-top:0.5rem;"3Hobuseteki kalkulaator lehelt 3a href="' + nuvalloURL + '" target="_blank" rel="noopener"3Nuvallo3/a33/p3'; // PLZ validation plzInput.addEventListener('input', function() { this.value = this.value.replace(/\D/g, '').slice(0, 5); updateGoBtn(); }); plzInput.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !goBtn.disabled) checkWeather(); }); window.selectHorse = function(btn) { document.querySelectorAll('.horse-type-btn').forEach(function(b) { b.classList.remove('active'); }); btn.classList.add('active'); selectedHorse = btn.getAttribute('data-type'); updateGoBtn(); }; function updateGoBtn() { goBtn.disabled = !(selectedHorse && plzInput.value.length === 5); } window.checkWeather = function() { var plz = plzInput.value; if (!selectedHorse || plz.length !== 5) return; show('loading'); hide('result-card'); hide('error-msg'); // Step 1: geocode the PLZ via Open-Meteo geocoding fetch('https://geocoding-api.open-meteo.com/v1/search?name=' + plz + '&count=1&language=de&country=DE') .then(function(r) { return r.json(); }) .then(function(geo) { if (!geo.results || !geo.results.length) throw new Error('Postiindeksit ei leitud. Palun kontrolli oma sisestust.'); var loc = geo.results[0]; // Step 2: fetch weather return fetch( 'https://api.open-meteo.com/v1/forecast?latitude=' + loc.latitude + '&longitude=' + loc.longitude + '&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m,precipitation_probability,weather_code' + '&timezone=Europe/Berlin&forecast_days=2' ).then(function(r) { return r.json(); }).then(function(weather) { return { location: loc, weather: weather }; }); }) .then(function(data) { hide('loading'); renderResult(data.weather, data.location); }) .catch(function(err) { hide('loading'); var msg = document.getElementById('error-msg'); msg.textContent = err.message || 'Viga ilmaandmete laadimisel.'; show('error-msg'); }); }; function renderResult(weather, location) { // Get tonight's data: today 18:00 – tomorrow 06:00 var hours = weather.hourly.time; var today = new Date(hours[0]).toISOString().slice(0, 10); var tomorrow = new Date(new Date(today).getTime() + 86400000).toISOString().slice(0, 10); var nightIndices = []; for (var i = 0; i < hours.length; i++) { var dt = new Date(hours[i]); var day = dt.toISOString().slice(0, 10); var h = dt.getHours(); if ((day === today && h >= 18) || (day === tomorrow && h <= 6)) { nightIndices.push(i); } } if (!nightIndices.length) { // fallback: use last 6 hours of the first day for (var j = 18; j < 24 && j < hours.length; j++) nightIndices.push(j); } var temps = nightIndices.map(function(i) { return weather.hourly.temperature_2m[i]; }); var winds = nightIndices.map(function(i) { return weather.hourly.wind_speed_10m[i]; }); var humids = nightIndices.map(function(i) { return weather.hourly.relative_humidity_2m[i]; }); var rains = nightIndices.map(function(i) { return weather.hourly.precipitation_probability[i]; }); var codes = nightIndices.map(function(i) { return weather.hourly.weather_code[i]; }); var avgTemp = avg(temps); var minTemp = Math.min.apply(null, temps); var avgWind = avg(winds); var avgHumid = avg(humids); var maxRain = Math.max.apply(null, rains); var mainCode = codes[Math.floor(codes.length / 2)]; // Compute "feels like" (simplified wind chill) var feelsLike = avgTemp; if (avgTemp <= 10 && avgWind > 5) { feelsLike = 13.12 + 0.6215 * avgTemp - 11.37 * Math.pow(avgWind, 0.16) + 0.3965 * avgTemp * Math.pow(avgWind, 0.16); } // Recommendation logic var rec = recommend(feelsLike, minTemp, avgWind, maxRain, avgHumid, selectedHorse); // Render recommendation document.getElementById('rec-icon').textContent = rec.icon; document.getElementById('rec-title').textContent = rec.title; document.getElementById('rec-detail').textContent = rec.detail; // Fill bar (0–5 warmth) var bar = document.getElementById('fill-bar'); bar.innerHTML = ''; for (var d = 0; d < 5; d++) { var dot = document.createElement('span'); dot.className = 'fill-dot' + (d < rec.warmth ? ' filled' : ''); bar.appendChild(dot); } // Render weather grid var grid = document.getElementById('weather-grid'); grid.innerHTML = [ weatherItem('🌡️', 'Temperatuur', round(minTemp) + '–' + round(Math.max.apply(null, temps)) + ' °C'), weatherItem('🌬️', 'Tuul', round(avgWind) + ' km/h'), weatherItem('💧', 'Vihmaoht', maxRain + ' %'), weatherItem(weatherIcon(mainCode), 'Ilm', weatherLabel(mainCode)) ].join(''); show('result-card'); } function recommend(feelsLike, minTemp, wind, rain, humidity, horse) { // Horse sensitivity offset: young = 0, old = -3, ekzem = -5 (feels colder) var offset = horse === 'alt' ? -3 : horse === 'ekzem' ? -5 : 0; var effective = feelsLike + offset; // Rain factor var needsRain = rain > 40; if (effective > 15) { return { icon: '☀️', title: 'Tekki pole vaja', warmth: 0, detail: 'Pehme öö — sinu hobune saab ilma tekita hästi hakkama.' + (needsRain ? ' Vihma korral valikuliselt vihmatekk.' : '') }; } if (effective > 10) { return { icon: needsRain ? '🌧️' : '🍃', title: needsRain ? 'Kerge vihmatekk' : 'Kerge vahehooaja tekk', warmth: 1, detail: 'Jahe öö (' + round(minTemp) + ' °C). Piisab õhukesest tekist (0–50g).' + (needsRain ? ' Soovitatav on veekindel tekk.' : '') }; } if (effective > 5) { return { icon: '🧥', title: needsRain ? 'Keskmine vihmatekk' : 'Keskmine tekk (100g)', warmth: 2, detail: 'Karge öö (' + round(minTemp) + ' °C). 100g täidis hoiab sooja.' + (wind > 20 ? ' Arvesta tuulekaitsega!' : '') }; } if (effective > -2) { return { icon: '❄️', title: 'Soe talvetekk (200g)', warmth: 3, detail: 'Külm öö (' + round(minTemp) + ' °C). Soovitatav on 200g täidis.' + (needsRain ? ' Veekindlus on oluline!' : '') }; } if (effective > -10) { return { icon: '🥶', title: 'Paks talvetekk (300g)', warmth: 4, detail: 'Väga külm öö (' + round(minTemp) + ' °C). Vähemalt 300g, vajadusel kaelaosaga.' }; } return { icon: '🧊', title: 'Maksimaalne kaitse (400g+)', warmth: 5, detail: 'Äärmiselt külm (' + round(minTemp) + ' °C)! Soovitatav 400g koos kaelaosa ja alustekiga.' }; } function weatherItem(icon, label, value) { return '3div class="weather-item"33span class="weather-item-icon"3' + icon + '3/span3' + '3div class="weather-item-info"33div class="weather-item-label"3' + label + '3/div3' + '3div class="weather-item-value"3' + value + '3/div33/div33/div3'; } function weatherIcon(code) { if (code <= 1) return '☀️'; if (code <= 3) return '⛅'; if (code <= 48) return '🌫️'; if (code <= 67) return '🌧️'; if (code <= 77) return '🌨️'; if (code <= 82) return '🌧️'; if (code <= 86) return '❄️'; return '⛈️'; } function weatherLabel(code) { if (code <= 1) return 'Selge'; if (code <= 3) return 'Pilvine'; if (code <= 48) return 'Udu'; if (code <= 57) return 'Uduvihm'; if (code <= 67) return 'Vihm'; if (code <= 77) return 'Lumi'; if (code <= 82) return 'Hoovihm'; if (code <= 86) return 'Lumehood'; return 'Äike'; } function avg(arr) { return arr.reduce(function(a, b) { return a + b; }, 0) / arr.length; } function round(n) { return Math.round(n * 10) / 10; } function show(id) { document.getElementById(id).classList.add('visible'); } function hide(id) { document.getElementById(id).classList.remove('visible'); } window.resetForm = function() { hide('result-card'); hide('error-msg'); document.getElementById('form-section').scrollIntoView({ behavior: 'smooth' }); }; window.copyEmbed = function() { var ta = document.getElementById('embed-code'); ta.select(); document.execCommand('copy'); var btn = ta.nextElementSibling; btn.textContent = '✓ Kopeeritud!'; setTimeout(function() { btn.textContent = 'Kopeeri kood'; }, 2000); }; })(); 3/script3 3/div3