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