diff --git a/BanYaroGo.entitlements b/BanYaroGo.entitlements
index 2ab14a2..03d07da 100644
--- a/BanYaroGo.entitlements
+++ b/BanYaroGo.entitlements
@@ -6,5 +6,7 @@
com.apple.developer.healthkit.access
+ com.apple.developer.weatherkit
+
diff --git a/BanYaroGo/Support/ColorHex.swift b/BanYaroGo/Support/ColorHex.swift
new file mode 100644
index 0000000..044b1d7
--- /dev/null
+++ b/BanYaroGo/Support/ColorHex.swift
@@ -0,0 +1,20 @@
+import SwiftUI
+
+extension Color {
+ /// Creates a Color from a 6-digit hex string (e.g. "10B981" or "#10B981").
+ /// Falls back to system gray on bad input.
+ init(hex: String) {
+ let cleaned = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#"))
+ var value: UInt64 = 0
+ Scanner(string: cleaned).scanHexInt64(&value)
+
+ guard cleaned.count == 6 else {
+ self.init(.sRGB, red: 0.5, green: 0.5, blue: 0.5)
+ return
+ }
+ let r = Double((value >> 16) & 0xFF) / 255
+ let g = Double((value >> 8) & 0xFF) / 255
+ let b = Double(value & 0xFF) / 255
+ self.init(.sRGB, red: r, green: g, blue: b)
+ }
+}
diff --git a/BanYaroGo/Support/GassiWetter.swift b/BanYaroGo/Support/GassiWetter.swift
new file mode 100644
index 0000000..50bfdf4
--- /dev/null
+++ b/BanYaroGo/Support/GassiWetter.swift
@@ -0,0 +1,182 @@
+import Foundation
+
+/// Gassi-Wetter-Bewertung — 1:1-Port der Logik aus banyaro PWA (wetter.js).
+/// Asphalt-Formel, Gassi-Score-Komposition, Schnüffel-Index und Zecken-
+/// Heuristik identisch, damit PWA und Go App dieselbe Empfehlung geben.
+enum GassiWetter {
+
+ // MARK: - Asphalt-Temperatur (aus Lufttemp + UV-Index)
+
+ static func asphaltTemp(airMax: Double, uvMax: Double) -> Double {
+ // UV-Bonus skaliert mit Temperatur: unter 5°C kaum Aufheizung, ab 30°C voll
+ let tFactor = max(0.0, min(1.0, (airMax - 5) / 25))
+ let bonus = min(uvMax * 3.0 * tFactor, 30.0)
+ return (airMax + bonus).rounded(toPlaces: 1)
+ }
+
+ enum AsphaltLevel {
+ case safe, warm, hot, danger
+
+ var label: String {
+ switch self {
+ case .safe: return "Unbedenklich"
+ case .warm: return "Warm — kurze Runden ok"
+ case .hot: return "Heiß — Pfoten verbrennen"
+ case .danger: return "Gefährlich — gar nicht laufen"
+ }
+ }
+
+ var advice: String? {
+ switch self {
+ case .safe: return nil
+ case .warm: return "Schatten suchen, Pausen einlegen."
+ case .hot: return "Gras oder Schatten bevorzugen — Asphalt vermeiden."
+ case .danger: return "Nur Morgens/Abends rausgehen, mittags drinnen."
+ }
+ }
+ }
+
+ static func asphaltLevel(for asphalt: Double) -> AsphaltLevel {
+ switch asphalt {
+ case ..<30: return .safe
+ case ..<40: return .warm
+ case ..<55: return .hot
+ default: return .danger
+ }
+ }
+
+ // MARK: - Gassi-Score (1–10)
+
+ static func gassiScore(
+ tempMax: Double,
+ precipProb: Int,
+ windKmh: Double,
+ asphalt: Double,
+ thunderstorm: Bool
+ ) -> Int {
+ var score = 10
+
+ // Temperatur (ideal 10–20°C)
+ if tempMax > 30 { score -= 3 }
+ else if tempMax > 25 { score -= 1 }
+ else if tempMax < 0 { score -= 3 }
+ else if tempMax < 5 { score -= 1 }
+
+ // Regen
+ if precipProb > 70 { score -= 3 }
+ else if precipProb > 40 { score -= 2 }
+ else if precipProb > 20 { score -= 1 }
+
+ // Wind
+ if windKmh > 60 { score -= 2 }
+ else if windKmh > 40 { score -= 1 }
+
+ // Asphalt
+ if asphalt > 55 { score -= 2 }
+ else if asphalt > 45 { score -= 1 }
+
+ // Gewitter
+ if thunderstorm { score -= 3 }
+
+ return max(1, min(10, score))
+ }
+
+ struct GassiScoreBadge {
+ let score: Int
+ let label: String
+ let colorHex: String
+
+ init(score: Int) {
+ self.score = score
+ if score >= 8 {
+ label = "Toller Gassi-Tag!"
+ colorHex = "10B981"
+ } else if score >= 5 {
+ label = "Geht so"
+ colorHex = "F59E0B"
+ } else {
+ label = "Lieber drinbleiben"
+ colorHex = "EF4444"
+ }
+ }
+ }
+
+ // MARK: - Schnüffel-Index
+
+ struct SchnueffelIndex {
+ let label: String
+ let emoji: String
+ let colorHex: String
+ }
+
+ static func schnueffelIndex(tempMax: Double, precipProb: Int) -> SchnueffelIndex {
+ let feucht: Feuchte = precipProb > 60 ? .feucht
+ : precipProb > 30 ? .leicht
+ : .trocken
+
+ if feucht == .feucht, tempMax >= 10, tempMax <= 18 {
+ return SchnueffelIndex(label: "Exzellent", emoji: "👃", colorHex: "10B981")
+ }
+ if feucht == .feucht, tempMax > 10, tempMax <= 22 {
+ return SchnueffelIndex(label: "Sehr gut", emoji: "👃", colorHex: "34D399")
+ }
+ if tempMax < 5 {
+ return SchnueffelIndex(label: "Gut (kalte Luft trägt Gerüche)", emoji: "🌬️", colorHex: "60A5FA")
+ }
+ if feucht == .leicht, tempMax >= 10, tempMax <= 22 {
+ return SchnueffelIndex(label: "Gut", emoji: "👃", colorHex: "4CAF50")
+ }
+ if feucht == .trocken, tempMax > 25 {
+ return SchnueffelIndex(label: "Schwach", emoji: "💨", colorHex: "94A3B8")
+ }
+ return SchnueffelIndex(label: "Mittel", emoji: "🌫", colorHex: "F59E0B")
+ }
+
+ private enum Feuchte { case feucht, leicht, trocken }
+
+ // MARK: - Zecken-Risiko
+
+ enum TickRisk {
+ case none, low, medium, high
+
+ var label: String {
+ switch self {
+ case .none: return "Keine"
+ case .low: return "Niedrig"
+ case .medium: return "Mittel"
+ case .high: return "Hoch"
+ }
+ }
+
+ var colorHex: String {
+ switch self {
+ case .none: return "94A3B8"
+ case .low: return "10B981"
+ case .medium: return "F59E0B"
+ case .high: return "EF4444"
+ }
+ }
+ }
+
+ static func tickRisk(tempMax: Double, month: Int) -> TickRisk {
+ // Wie in banyaro: nur März-Oktober relevant
+ guard (3...10).contains(month) else { return .none }
+ if tempMax <= 7 { return .none }
+ if tempMax > 20 { return .high }
+ if tempMax > 12 { return .medium }
+ return .low
+ }
+
+ // MARK: - Pfoten-Kälteschutz
+
+ static func pawColdProtection(tempMin: Double) -> Bool {
+ tempMin <= 0
+ }
+}
+
+private extension Double {
+ func rounded(toPlaces places: Int) -> Double {
+ let multiplier = pow(10.0, Double(places))
+ return (self * multiplier).rounded() / multiplier
+ }
+}
diff --git a/BanYaroGo/Views/WetterView.swift b/BanYaroGo/Views/WetterView.swift
index 3693814..3598049 100644
--- a/BanYaroGo/Views/WetterView.swift
+++ b/BanYaroGo/Views/WetterView.swift
@@ -1,31 +1,23 @@
import SwiftUI
+import WeatherKit
import CoreLocation
struct WetterView: View {
@State private var location = OneShotLocation()
- @State private var forecast: WeatherForecast?
+ @State private var weather: Weather?
@State private var isLoading = false
@State private var errorMessage: String?
+ @State private var selectedDayIndex = 0
var body: some View {
Group {
- if let forecast {
- List {
- Section {
- ForEach(Array(forecast.days.prefix(7))) { day in
- WeatherDayRow(day: day)
- }
- } footer: {
- Text("Vorhersage von Open-Meteo. Hunde-Tipps basieren auf maximaler Tagestemperatur und Asphalt-Hitze.")
- .font(.caption2)
- .foregroundStyle(.tertiary)
- }
- }
+ if let weather {
+ content(weather: weather)
} else if isLoading {
ProgressView("Lade Wetter…")
} else if let errorMessage {
ContentUnavailableView(
- "Wetter konnte nicht geladen werden",
+ "Konnte nicht laden",
systemImage: "cloud.slash",
description: Text(errorMessage)
)
@@ -33,140 +25,321 @@ struct WetterView: View {
ContentUnavailableView(
"Kein Standort",
systemImage: "location.slash",
- description: Text(location.error ?? "Bitte Standort erlauben.")
+ description: Text(location.error ?? "")
)
} else {
ProgressView("Hole Standort…")
}
}
- .navigationTitle("Wetter")
+ .navigationTitle("Gassi-Wetter")
.navigationBarTitleDisplayMode(.inline)
- .task {
- location.request()
- }
+ .task { location.request() }
.onChange(of: location.coordinate?.latitude) { _, _ in
- Task { await load() }
+ Task { await loadWeather() }
}
- .refreshable { await load() }
+ .refreshable { await loadWeather() }
}
- private func load() async {
+ private func content(weather: Weather) -> some View {
+ let days = Array(weather.dailyForecast.prefix(7))
+ let safeIndex = max(0, min(selectedDayIndex, days.count - 1))
+
+ return ScrollView {
+ VStack(spacing: 14) {
+ if !days.isEmpty {
+ dayPicker(days: days)
+ let day = days[safeIndex]
+ let metrics = DayMetrics(day: day)
+
+ gassiScoreBadge(metrics: metrics)
+ summaryCard(day: day, metrics: metrics)
+ statsGrid(metrics: metrics)
+ hundeHinweise(day: day, metrics: metrics)
+ schnueffelCard(metrics: metrics)
+ }
+
+ Text("Wetterdaten von Apple WeatherKit. Gassi-Score, Asphalt-Temperatur und Hunde-Hinweise werden lokal berechnet — identische Formeln wie banyaro.app.")
+ .font(.caption2)
+ .foregroundStyle(.tertiary)
+ .padding(.top, 8)
+ }
+ .padding()
+ }
+ }
+
+ // MARK: - Day picker
+
+ private func dayPicker(days: [DayWeather]) -> some View {
+ ScrollView(.horizontal, showsIndicators: false) {
+ HStack(spacing: 8) {
+ ForEach(Array(days.enumerated()), id: \.offset) { i, day in
+ Button {
+ selectedDayIndex = i
+ } label: {
+ VStack(spacing: 4) {
+ Text(dayLabel(day.date, index: i))
+ .font(.caption.bold())
+ Image(systemName: day.symbolName)
+ .symbolRenderingMode(.multicolor)
+ .font(.title3)
+ Text("\(Int(day.highTemperature.converted(to: .celsius).value.rounded()))°")
+ .font(.subheadline.bold().monospacedDigit())
+ }
+ .frame(width: 56)
+ .padding(.vertical, 10)
+ .background(
+ i == selectedDayIndex ? Color.accentColor : Color.secondary.opacity(0.12),
+ in: RoundedRectangle(cornerRadius: 12)
+ )
+ .foregroundStyle(i == selectedDayIndex ? .white : .primary)
+ }
+ .buttonStyle(.plain)
+ }
+ }
+ }
+ }
+
+ private func dayLabel(_ date: Date, index: Int) -> String {
+ if index == 0 { return "Heute" }
+ if index == 1 { return "Morgen" }
+ let f = DateFormatter()
+ f.locale = Locale(identifier: "de_DE")
+ f.dateFormat = "EEE"
+ return f.string(from: date)
+ }
+
+ // MARK: - Gassi-Score Badge
+
+ private func gassiScoreBadge(metrics: DayMetrics) -> some View {
+ let badge = GassiWetter.GassiScoreBadge(score: metrics.gassiScore)
+ let color = Color(hex: badge.colorHex)
+ return HStack(spacing: 14) {
+ Text("🐾").font(.system(size: 32))
+ VStack(alignment: .leading, spacing: 2) {
+ Text("Gassi-Score").font(.caption.bold()).foregroundStyle(.secondary)
+ HStack(alignment: .firstTextBaseline, spacing: 4) {
+ Text("\(badge.score)").font(.system(size: 40, weight: .heavy, design: .rounded))
+ Text("/ 10").font(.title3.bold()).foregroundStyle(.secondary)
+ }
+ Text(badge.label).font(.subheadline.bold()).foregroundStyle(color)
+ }
+ Spacer()
+ }
+ .padding(16)
+ .background(color.opacity(0.15), in: RoundedRectangle(cornerRadius: 16))
+ .overlay(
+ RoundedRectangle(cornerRadius: 16)
+ .stroke(color.opacity(0.5), lineWidth: 1.5)
+ )
+ }
+
+ // MARK: - Summary card
+
+ private func summaryCard(day: DayWeather, metrics: DayMetrics) -> some View {
+ HStack(spacing: 14) {
+ Image(systemName: day.symbolName)
+ .symbolRenderingMode(.multicolor)
+ .font(.system(size: 56))
+ VStack(alignment: .leading, spacing: 2) {
+ Text(day.condition.description)
+ .font(.headline)
+ HStack(spacing: 6) {
+ Text("\(Int(metrics.tempMax.rounded()))°").font(.title3.bold().monospacedDigit())
+ Text("/ \(Int(metrics.tempMin.rounded()))°")
+ .font(.subheadline.monospacedDigit())
+ .foregroundStyle(.secondary)
+ }
+ }
+ Spacer()
+ }
+ .padding(16)
+ .background(.background.secondary, in: RoundedRectangle(cornerRadius: 16))
+ }
+
+ // MARK: - Stats grid
+
+ private func statsGrid(metrics: DayMetrics) -> some View {
+ let cols = [GridItem(.flexible()), GridItem(.flexible())]
+ return LazyVGrid(columns: cols, spacing: 10) {
+ statCell(icon: "umbrella.fill", color: .blue,
+ value: "\(metrics.precipPct) %", label: "Niederschlag")
+ statCell(icon: "wind", color: .gray,
+ value: "\(Int(metrics.windKmh.rounded())) km/h", label: "Wind")
+ statCell(icon: "sun.max.fill", color: .orange,
+ value: "\(metrics.uvIndex)", label: "UV-Index")
+ statCell(icon: "thermometer.sun.fill", color: .red,
+ value: "\(Int(metrics.asphalt.rounded()))°", label: "Asphalt")
+ }
+ }
+
+ private func statCell(icon: String, color: Color, value: String, label: String) -> some View {
+ HStack(spacing: 10) {
+ Image(systemName: icon)
+ .foregroundStyle(color)
+ .frame(width: 28)
+ VStack(alignment: .leading, spacing: 2) {
+ Text(value).font(.subheadline.bold().monospacedDigit())
+ Text(label).font(.caption).foregroundStyle(.secondary)
+ }
+ Spacer()
+ }
+ .padding(12)
+ .background(.background.secondary, in: RoundedRectangle(cornerRadius: 12))
+ }
+
+ // MARK: - Hunde-Hinweise
+
+ private func hundeHinweise(day: DayWeather, metrics: DayMetrics) -> some View {
+ VStack(alignment: .leading, spacing: 8) {
+ Label("Hunde-Hinweise", systemImage: "pawprint.fill")
+ .font(.headline)
+ .foregroundStyle(Color.accentColor)
+
+ // Asphalt
+ let level = GassiWetter.asphaltLevel(for: metrics.asphalt)
+ hintBox(
+ icon: "thermometer.sun.fill",
+ color: asphaltColor(for: level),
+ title: "Asphalt ~\(Int(metrics.asphalt.rounded()))°C — \(level.label)",
+ detail: level.advice
+ )
+
+ // Pfoten-Kälteschutz
+ if GassiWetter.pawColdProtection(tempMin: metrics.tempMin) {
+ hintBox(
+ icon: "snowflake",
+ color: .blue,
+ title: "Pfoten-Kälteschutz",
+ detail: "Eis und Streusalz reizen die Pfoten. Pfotenpflege empfohlen, ggf. Schuhe."
+ )
+ }
+
+ // Gewitter
+ if metrics.thunderstorm {
+ hintBox(
+ icon: "cloud.bolt.fill",
+ color: .purple,
+ title: "Gewitter erwartet",
+ detail: "Hunde reagieren oft sensibel. Sichere, ruhige Umgebung schaffen."
+ )
+ }
+
+ // Zecken
+ let month = Calendar.current.component(.month, from: day.date)
+ let tick = GassiWetter.tickRisk(tempMax: metrics.tempMax, month: month)
+ if tick != .none {
+ hintBox(
+ icon: "ant.fill",
+ color: Color(hex: tick.colorHex),
+ title: "Zecken-Risiko: \(tick.label)",
+ detail: tick == .high ? "Nach jedem Spaziergang absuchen, Schutzmittel verwenden." : nil
+ )
+ }
+ }
+ }
+
+ private func hintBox(icon: String, color: Color, title: String, detail: String?) -> some View {
+ HStack(alignment: .top, spacing: 10) {
+ Image(systemName: icon)
+ .foregroundStyle(color)
+ .frame(width: 24)
+ VStack(alignment: .leading, spacing: 2) {
+ Text(title).font(.subheadline.bold()).foregroundStyle(color)
+ if let detail {
+ Text(detail).font(.caption).foregroundStyle(.secondary)
+ }
+ }
+ Spacer()
+ }
+ .padding(12)
+ .background(color.opacity(0.12), in: RoundedRectangle(cornerRadius: 12))
+ .overlay(
+ RoundedRectangle(cornerRadius: 12)
+ .stroke(color.opacity(0.35), lineWidth: 1)
+ )
+ }
+
+ private func asphaltColor(for level: GassiWetter.AsphaltLevel) -> Color {
+ switch level {
+ case .safe: return .green
+ case .warm: return .yellow
+ case .hot: return .orange
+ case .danger: return .red
+ }
+ }
+
+ // MARK: - Schnüffel-Index
+
+ private func schnueffelCard(metrics: DayMetrics) -> some View {
+ let idx = GassiWetter.schnueffelIndex(tempMax: metrics.tempMax, precipProb: metrics.precipPct)
+ let color = Color(hex: idx.colorHex)
+ return HStack(spacing: 12) {
+ Text(idx.emoji).font(.title)
+ VStack(alignment: .leading, spacing: 2) {
+ Text("Schnüffel-Index").font(.caption.bold()).foregroundStyle(.secondary)
+ Text(idx.label).font(.subheadline.bold()).foregroundStyle(color)
+ }
+ Spacer()
+ }
+ .padding(14)
+ .background(color.opacity(0.12), in: RoundedRectangle(cornerRadius: 12))
+ }
+
+ // MARK: - Loading
+
+ private func loadWeather() async {
guard let coord = location.coordinate else { return }
isLoading = true
errorMessage = nil
defer { isLoading = false }
+ let loc = CLLocation(latitude: coord.latitude, longitude: coord.longitude)
do {
- forecast = try await APIClient.shared.get(
- "/api/weather/forecast?lat=\(coord.latitude)&lon=\(coord.longitude)"
- )
+ weather = try await WeatherService.shared.weather(for: loc)
} catch {
errorMessage = error.localizedDescription
}
}
}
-private struct WeatherDayRow: View {
- let day: WeatherDay
+// MARK: - Day metrics derived from DayWeather
- private var dateLabel: String {
- if day.date == today { return "Heute" }
- if day.date == tomorrow { return "Morgen" }
- return day.wday ?? day.date
- }
+private struct DayMetrics {
+ let tempMax: Double
+ let tempMin: Double
+ let precipPct: Int
+ let windKmh: Double
+ let uvIndex: Int
+ let thunderstorm: Bool
+ let asphalt: Double
+ let gassiScore: Int
- private var today: String {
- let f = DateFormatter(); f.dateFormat = "yyyy-MM-dd"
- return f.string(from: .now)
- }
- private var tomorrow: String {
- let f = DateFormatter(); f.dateFormat = "yyyy-MM-dd"
- let d = Calendar.current.date(byAdding: .day, value: 1, to: .now) ?? .now
- return f.string(from: d)
- }
+ init(day: DayWeather) {
+ let tMax = day.highTemperature.converted(to: .celsius).value
+ let tMin = day.lowTemperature.converted(to: .celsius).value
+ let pct = Int((day.precipitationChance * 100).rounded())
+ let wind = day.wind.speed.converted(to: .kilometersPerHour).value
+ let uv = day.uvIndex.value
+ let thunder = [
+ WeatherCondition.thunderstorms,
+ .strongStorms,
+ .scatteredThunderstorms,
+ .isolatedThunderstorms
+ ].contains(day.condition)
+ let asphaltT = GassiWetter.asphaltTemp(airMax: tMax, uvMax: Double(uv))
- private var weatherSymbol: String {
- switch day.weathercode ?? 0 {
- case 0: return "sun.max.fill"
- case 1, 2: return "cloud.sun.fill"
- case 3: return "cloud.fill"
- case 45, 48: return "cloud.fog.fill"
- case 51...57: return "cloud.drizzle.fill"
- case 61...67: return "cloud.rain.fill"
- case 71...77: return "cloud.snow.fill"
- case 80...82: return "cloud.heavyrain.fill"
- case 95...99: return "cloud.bolt.rain.fill"
- default: return "cloud"
- }
- }
-
- var body: some View {
- VStack(alignment: .leading, spacing: 8) {
- HStack(spacing: 12) {
- Image(systemName: weatherSymbol)
- .font(.title2)
- .foregroundStyle(Color.accentColor)
- .frame(width: 36)
- VStack(alignment: .leading, spacing: 2) {
- Text(dateLabel).font(.headline)
- if let desc = day.desc { Text(desc).font(.caption).foregroundStyle(.secondary) }
- }
- Spacer()
- tempColumn
- }
-
- if !tips.isEmpty {
- VStack(alignment: .leading, spacing: 4) {
- ForEach(Array(tips.enumerated()), id: \.offset) { _, tip in
- Label(tip.text, systemImage: tip.icon)
- .font(.caption)
- .foregroundStyle(tip.color)
- }
- }
- .padding(.leading, 48)
- }
- }
- .padding(.vertical, 4)
- }
-
- private var tempColumn: some View {
- HStack(alignment: .firstTextBaseline, spacing: 6) {
- if let max = day.tempMax {
- Text("\(Int(max.rounded()))°").font(.headline.monospacedDigit())
- }
- if let min = day.tempMin {
- Text("\(Int(min.rounded()))°")
- .font(.subheadline.monospacedDigit())
- .foregroundStyle(.secondary)
- }
- }
- }
-
- private struct Tip {
- let text: String
- let icon: String
- let color: Color
- }
-
- private var tips: [Tip] {
- var result: [Tip] = []
- if let max = day.tempMax {
- if max >= 30 {
- result.append(Tip(text: "Extreme Hitze — Gassi nur früh morgens/abends", icon: "sun.dust.fill", color: .red))
- } else if max >= 25 {
- result.append(Tip(text: "Warm — Pfoten auf Asphalt prüfen", icon: "thermometer.sun", color: .orange))
- } else if max <= 0 {
- result.append(Tip(text: "Frost — Pfoten nach Streusalz abwischen", icon: "snowflake", color: .blue))
- }
- }
- if let asphalt = day.asphaltTemp, asphalt >= 50 {
- result.append(Tip(text: "Asphalt ~\(Int(asphalt.rounded()))°C — verbrennungsgefahr", icon: "flame.fill", color: .red))
- }
- if let zecken = day.zecken, zecken == "hoch" {
- result.append(Tip(text: "Hohe Zecken-Gefahr", icon: "ant.fill", color: .orange))
- }
- if let pp = day.precipProb, pp >= 70 {
- result.append(Tip(text: "Regen wahrscheinlich (\(pp) %)", icon: "umbrella.fill", color: .blue))
- }
- return result
+ self.tempMax = tMax
+ self.tempMin = tMin
+ self.precipPct = pct
+ self.windKmh = wind
+ self.uvIndex = uv
+ self.thunderstorm = thunder
+ self.asphalt = asphaltT
+ self.gassiScore = GassiWetter.gassiScore(
+ tempMax: tMax,
+ precipProb: pct,
+ windKmh: wind,
+ asphalt: asphaltT,
+ thunderstorm: thunder
+ )
}
}