Den Tages-Check aufs iPhone holen
Es gibt zwei Wege, und du kannst beide kombinieren. Ein echtes Widget auf dem Sperrbildschirm geht unter iOS nur über eine kleine Helfer-App, weil Apple Websites dort nicht zulässt. Ein App-Icon auf dem Home-Bildschirm bekommst du direkt aus Safari. Beides bleibt werbefrei und ohne Tracking.
Weg 1: Sperrbildschirm-Widget mit Scriptable
Scriptable ist eine kostenlose App, mit der sich kleine Skripte als Widget anzeigen lassen. Das Skript unten holt die Wetterdaten direkt auf deinem Gerät von Open-Meteo und rechnet lokal. Es sendet nichts an meaningful.dev, es gibt keinen Account.
- Lade Scriptable aus dem App Store.
- Öffne Scriptable, tippe auf das Plus oben rechts, füge den Skript-Text von unten ein (oder nutze die Datei) und benenne das Skript „Tages-Check“.
-
Optional: Trage im Skript oben bei
CONFIGdeine Stadt ein, dann braucht das Widget keinen Standortzugriff. Lässt du es leer, fragt es einmalig nach deinem Standort. - Halte den Sperrbildschirm gedrückt, tippe auf „Anpassen“, dann auf einen Widget-Bereich, wähle „Scriptable“ und darin das Skript „Tages-Check“.
Das Widget passt sich der gewählten Größe an: Der schmale Streifen direkt über der Uhr zeigt eine Zeile (Temperatur plus der wichtigste Punkt), das rechteckige Feld darunter zwei Zeilen mit Empfehlung und Werten. Für die einzeilige Variante wählst du beim Anpassen also den Platz oberhalb der Uhr.
Auf dem iPhone am einfachsten: „Skript kopieren“ tippen, dann in Scriptable ein neues Skript anlegen und einfügen.
// Tages-Check fuer Scriptable (iOS Sperrbildschirm- und Home-Widget)
// Quelle: meaningful.dev · Daten: Open-Meteo (Modelle DWD ICON, CAMS)
// Kein Tracking, kein Account. Das Skript holt die Wetterdaten direkt auf
// deinem Geraet und rechnet lokal. Nichts wird an meaningful.dev gesendet.
//
// Einrichtung:
// 1. Diese Datei komplett in ein neues Scriptable-Skript einfuegen.
// 2. Optional unten bei CONFIG eine feste Stadt eintragen. Sonst nutzt das
// Widget deinen Standort (einmalig Erlaubnis erforderlich).
// 3. Sperrbildschirm bearbeiten, Widget hinzufuegen, Scriptable waehlen,
// dieses Skript zuweisen.
// ===== CONFIG =====
const CONFIG = {
// Feste Koordinaten eintragen, um den Standortzugriff zu vermeiden.
// Beispiel Freiburg: lat: 47.99, lon: 7.84, ort: "Freiburg"
lat: null,
lon: null,
ort: null,
};
// ===== Standort =====
async function standort() {
if (CONFIG.lat != null && CONFIG.lon != null) {
return { lat: CONFIG.lat, lon: CONFIG.lon, name: CONFIG.ort || "Dein Ort" };
}
try {
Location.setAccuracyToThreeKilometers();
const p = await Location.current();
let name = "Dein Ort";
try {
const g = await Location.reverseGeocode(p.latitude, p.longitude);
name = (g[0] && (g[0].locality || g[0].administrativeArea)) || name;
} catch (e) {}
return { lat: p.latitude, lon: p.longitude, name };
} catch (e) {
// Fallback: geografische Mitte Deutschlands
return { lat: 51.16, lon: 10.45, name: "Deutschland" };
}
}
// ===== Daten =====
async function hole(url) {
const r = new Request(url);
return await r.loadJSON();
}
function stundeJetzt(times) {
const jetzt = new Date();
const p = (n) => String(n).padStart(2, "0");
const key = `${jetzt.getFullYear()}-${p(jetzt.getMonth() + 1)}-${p(jetzt.getDate())}T${p(jetzt.getHours())}:00`;
const i = times.indexOf(key);
return i >= 0 ? i : 0;
}
async function lage() {
const o = await standort();
const basis = `latitude=${o.lat}&longitude=${o.lon}&timezone=auto&forecast_days=1`;
const [w, a] = await Promise.all([
hole(`https://api.open-meteo.com/v1/forecast?${basis}&hourly=precipitation,weather_code&daily=uv_index_max,apparent_temperature_max,temperature_2m_max`),
hole(`https://air-quality-api.open-meteo.com/v1/air-quality?${basis}&hourly=grass_pollen,birch_pollen,ozone`),
]);
const wh = w.hourly, ah = a.hourly;
const jw = stundeJetzt(wh.time), ja = stundeJetzt(ah.time);
const restW = wh.time.map((_, i) => i).filter((i) => i >= jw);
const restA = ah.time.map((_, i) => i).filter((i) => i >= ja);
// Regen: erste Stunde mit >= 0.2 mm
let regen = null;
for (const i of restW) {
if ((wh.precipitation[i] ?? 0) >= 0.2) { regen = parseInt(wh.time[i].slice(11, 13), 10); break; }
}
// UV (Tageshoechstwert)
const uv = Math.round(w.daily.uv_index_max[0] ?? 0);
// Pollen: Gräser und Birke, hoechste Stufe (0..3)
const stufe = (v, s) => (v >= s[2] ? 3 : v >= s[1] ? 2 : v >= s[0] ? 1 : 0);
const gras = Math.max(...restA.map((i) => ah.grass_pollen?.[i] ?? 0), 0);
const birke = Math.max(...restA.map((i) => ah.birch_pollen?.[i] ?? 0), 0);
const pStufe = Math.max(stufe(gras, [1, 30, 50]), stufe(birke, [1, 11, 50]));
const pTxt = ["keine", "gering", "mittel", "hoch"][pStufe];
// Ozon
const o3 = Math.max(...restA.map((i) => ah.ozone?.[i] ?? 0), 0);
// Hitze (gefuehlt, Tageshoechstwert)
const gefuehlt = Math.round(w.daily.apparent_temperature_max[0] ?? 0);
const real = Math.round(w.daily.temperature_2m_max[0] ?? 0);
return { ort: o.name, regen, uv, pStufe, pTxt, o3, gefuehlt, real };
}
// ===== Bewertung: kurze Handlungszeile + Faktoren =====
function auswerten(l) {
const faktoren = [];
if (l.pStufe >= 2) faktoren.push(`Pollen ${l.pTxt}`);
if (l.uv >= 3) faktoren.push(`UV ${l.uv}`);
if (l.regen != null) faktoren.push(`Regen ab ${l.regen} Uhr`);
if (l.gefuehlt >= 27) faktoren.push(`gefühlt ${l.gefuehlt}°`);
if (l.o3 >= 180) faktoren.push(`Ozon hoch`);
// Wichtigste Handlung
let aktion = "Heute wenig zu beachten.";
if (l.gefuehlt >= 32) aktion = "Viel trinken, Mittagssonne meiden.";
else if (l.pStufe >= 2) aktion = "Vor 8 Uhr lüften, tagsüber Fenster zu.";
else if (l.uv >= 6) aktion = "Sonnencreme ab 15 Min draußen.";
else if (l.regen != null) aktion = `Regen ab etwa ${l.regen} Uhr.`;
else if (l.uv >= 3) aktion = "Bei längerem Aufenthalt Sonnenschutz.";
const stats = `${l.real}° · UV ${l.uv} · Pollen ${l.pTxt}`;
// Kürzester Ein-Zeiler für das schmale Inline-Widget neben der Uhr
let kurz;
if (l.gefuehlt >= 32) kurz = `${l.real}° · gefühlt ${l.gefuehlt}°`;
else if (l.pStufe >= 2) kurz = `${l.real}° · Pollen ${l.pTxt}`;
else if (l.regen != null) kurz = `${l.real}° · Regen ${l.regen} Uhr`;
else kurz = `${l.real}° · UV ${l.uv}`;
return { aktion, stats, faktoren, kurz };
}
// ===== Widget =====
function zeile(w, text, opts) {
const t = w.addText(text);
t.font = opts.font;
if (opts.color) t.textColor = opts.color;
if (opts.max) t.lineLimit = opts.max;
return t;
}
async function baue() {
const l = await lage();
const a = auswerten(l);
const family = config.widgetFamily || "accessoryRectangular";
const w = new ListWidget();
const gleichAktualisieren = () => (w.refreshAfterDate = new Date(Date.now() + 30 * 60 * 1000));
// Schmales Inline-Widget: eine Zeile neben der Uhr, vom System eingefärbt
if (family === "accessoryInline") {
zeile(w, a.kurz, { font: Font.systemFont(13), max: 1 });
gleichAktualisieren();
return w;
}
// Kreis-Widget: sehr klein, Temperatur plus UV
if (family === "accessoryCircular") {
w.addSpacer();
zeile(w, `${l.real}°`, { font: Font.semiboldSystemFont(18), max: 1 });
zeile(w, `UV ${l.uv}`, { font: Font.systemFont(10), max: 1 });
w.addSpacer();
gleichAktualisieren();
return w;
}
// Rechteckiges Sperrbildschirm-Widget: kompakt in zwei Zeilen
if (family === "accessoryRectangular") {
zeile(w, a.aktion, { font: Font.semiboldSystemFont(13), max: 2 });
zeile(w, a.stats, { font: Font.systemFont(12), max: 1 });
gleichAktualisieren();
return w;
}
// Home-Bildschirm: eigene Farben in der Markenwelt
w.backgroundColor = new Color("#17656b");
zeile(w, l.ort, { font: Font.semiboldSystemFont(14), color: new Color("#bfe0e2"), max: 1 });
w.addSpacer(4);
zeile(w, a.aktion, { font: Font.semiboldSystemFont(17), color: new Color("#ffffff"), max: 3 });
w.addSpacer(6);
zeile(w, a.stats, { font: Font.systemFont(12), color: new Color("#cfe6e7"), max: 1 });
if (family === "systemMedium" && a.faktoren.length) {
w.addSpacer(4);
zeile(w, a.faktoren.join(" · "), { font: Font.systemFont(12), color: new Color("#cfe6e7"), max: 2 });
}
w.addSpacer();
const q = zeile(w, "meaningful.dev", { font: Font.systemFont(9), color: new Color("#9ec7c9"), max: 1 });
q.textOpacity = 0.7;
gleichAktualisieren();
return w;
}
const widget = await baue();
if (config.runsInWidget) {
Script.setWidget(widget);
} else {
await widget.presentMedium(); // Vorschau beim manuellen Start
}
Script.complete();
Weg 2: App-Icon auf dem Home-Bildschirm
Öffne meaningful.dev in Safari, tippe unten auf das Teilen-Symbol und wähle „Zum Home-Bildschirm“. Danach startet der Tages-Check als eigenes Icon im Vollbild, ohne Browserleiste. Das ist kein Sperrbildschirm-Widget, aber der schnellste Weg zum täglichen Blick.