struct HourData { hour: string, temp: int, icon-type: int, is-now: bool, } // ═══ API Key Dialog ═════════════════════════════════════════ export component ApiKeyDialog inherits Window { title: "OpenWeatherMap API Key"; preferred-width: 480px; preferred-height: 320px; background: @linear-gradient(180deg, #1a1a2e, #0f0f1e); in-out property api-key-input: ""; callback submit-key(string); callback skip; VerticalLayout { padding: 40px; spacing: 20px; alignment: center; Text { text: "Configurazione API Key"; color: white; font-size: 24px; font-weight: 600; horizontal-alignment: center; } Text { text: "Inserisci la tua chiave API di OpenWeatherMap\nper visualizzare i dati meteo in tempo reale."; color: #ffffffaa; font-size: 13px; horizontal-alignment: center; wrap: word-wrap; } Rectangle { height: 10px; } // Input field Rectangle { height: 50px; border-radius: 12px; background: #ffffff0a; border-width: 1px; border-color: #ffffff22; HorizontalLayout { padding: 14px; input := TextInput { text <=> root.api-key-input; font-size: 14px; color: white; horizontal-alignment: left; single-line: true; accepted => { root.submit-key(self.text); } } } } Text { text: "Ottieni una chiave gratuita su:\nopenweathermap.org/api"; color: #66bbff; font-size: 11px; horizontal-alignment: center; } Rectangle { vertical-stretch: 1; } HorizontalLayout { spacing: 12px; Rectangle { horizontal-stretch: 1; height: 44px; border-radius: 12px; background: #ffffff12; border-width: 1px; border-color: #ffffff1a; Text { text: "Salta (usa dati fake)"; color: #ffffffaa; font-size: 13px; vertical-alignment: center; horizontal-alignment: center; } TouchArea { clicked => { root.skip(); } } } Rectangle { horizontal-stretch: 1; height: 44px; border-radius: 12px; background: @linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); drop-shadow-blur: 12px; drop-shadow-color: #4facfe44; Text { text: "Salva e Continua"; color: white; font-size: 13px; font-weight: 600; vertical-alignment: center; horizontal-alignment: center; } TouchArea { clicked => { root.submit-key(root.api-key-input); } } } } } } // ─── Decorative Cloud ──────────────────────────────────────── component CloudShape inherits Rectangle { in property tick: 0; in property speed: 0.3; in property phase: 0; in property base-opacity: 0.25; background: transparent; opacity: base-opacity; property bob: Math.sin((tick * speed * 0.7 + phase) * 1rad) * 6px; Rectangle { x: parent.width * 0.15; y: parent.height * 0.35 + parent.bob; width: parent.width * 0.7; height: parent.height * 0.55; border-radius: self.height / 2; background: white; } Rectangle { x: parent.width * 0.30; y: parent.height * 0.05 + parent.bob; width: parent.width * 0.4; height: parent.height * 0.55; border-radius: self.height / 2; background: white; } Rectangle { x: parent.width * 0.05; y: parent.height * 0.40 + parent.bob; width: parent.width * 0.32; height: parent.height * 0.42; border-radius: self.height / 2; background: white; } Rectangle { x: parent.width * 0.58; y: parent.height * 0.28 + parent.bob; width: parent.width * 0.32; height: parent.height * 0.42; border-radius: self.height / 2; background: white; } } // ─── Rain Drop ─────────────────────────────────────────────── component RainDrop inherits Rectangle { in property tick; in property speed: 3.0; in property phase: 0; width: 2px; height: 18px; border-radius: 1px; background: @linear-gradient(180deg, #aaddff00, #aaddffaa); opacity: Math.max(0, Math.sin((tick * speed + phase) * 1rad)) * 0.6; } // ═══ Weather Display Icons (drawn with shapes) ══════════════ // ─── Sun Display ───────────────────────────────────────────── component SunDisplay inherits Rectangle { in property tick; background: transparent; // Pulsing outer ring 1 Rectangle { width: parent.width * 0.92; height: parent.height * 0.92; x: parent.width * 0.04; y: parent.height * 0.04; border-radius: self.width / 2; background: transparent; border-width: 1.5px; border-color: #FFD70022; opacity: 0.4 + Math.sin(tick * 1.5rad) * 0.4; } // Pulsing outer ring 2 (counter-phase) Rectangle { width: parent.width * 0.82; height: parent.height * 0.82; x: parent.width * 0.09; y: parent.height * 0.09; border-radius: self.width / 2; background: transparent; border-width: 1.5px; border-color: #FFD70033; opacity: 0.5 + Math.sin((tick * 2.0 + 1.0) * 1rad) * 0.35; } // Pulsing outer ring 3 Rectangle { width: parent.width * 0.72; height: parent.height * 0.72; x: parent.width * 0.14; y: parent.height * 0.14; border-radius: self.width / 2; background: transparent; border-width: 1px; border-color: #FFD70044; opacity: 0.4 + Math.sin((tick * 2.5 + 2.0) * 1rad) * 0.4; } // Warm glow halo Rectangle { width: parent.width * 0.65; height: parent.height * 0.65; x: parent.width * 0.175; y: parent.height * 0.175; border-radius: self.width / 2; background: #FFD70018; drop-shadow-blur: 15px; drop-shadow-color: #FFD70033; opacity: 0.6 + Math.sin(tick * 1.8rad) * 0.3; } // Main circle Rectangle { width: parent.width * 0.50; height: parent.height * 0.50; x: parent.width * 0.25; y: parent.height * 0.25; border-radius: self.width / 2; background: @radial-gradient(circle, #FFE066 0%, #FFD700 50%, #FFA500 100%); drop-shadow-blur: 22px; drop-shadow-color: #FFD70088; } // Bright core Rectangle { width: parent.width * 0.22; height: parent.height * 0.22; x: parent.width * 0.39; y: parent.height * 0.39; border-radius: self.width / 2; background: #FFF8E1; opacity: 0.55 + Math.sin(tick * 4rad) * 0.3; } } // ─── Cloud Display ─────────────────────────────────────────── component CloudDisplay inherits Rectangle { in property tick; background: transparent; property bob: Math.sin(tick * 0.8rad) * 3px; Rectangle { x: parent.width * 0.10; y: parent.height * 0.45 + parent.bob; width: parent.width * 0.80; height: parent.height * 0.42; border-radius: self.height / 2; background: @linear-gradient(180deg, #ffffff, #e0e0ee); drop-shadow-blur: 12px; drop-shadow-color: #00000015; } Rectangle { x: parent.width * 0.14; y: parent.height * 0.28 + parent.bob; width: parent.width * 0.34; height: parent.height * 0.42; border-radius: self.height / 2; background: #ffffff; } Rectangle { x: parent.width * 0.30; y: parent.height * 0.08 + parent.bob; width: parent.width * 0.40; height: parent.height * 0.52; border-radius: self.height / 2; background: #ffffff; } Rectangle { x: parent.width * 0.52; y: parent.height * 0.22 + parent.bob; width: parent.width * 0.34; height: parent.height * 0.42; border-radius: self.height / 2; background: #fafaff; } } // ─── Rain Display ──────────────────────────────────────────── component RainDisplay inherits Rectangle { in property tick; background: transparent; property bob: Math.sin(tick * 0.6rad) * 2px; // Dark cloud Rectangle { x: parent.width * 0.10; y: parent.height * 0.10 + bob; width: parent.width * 0.80; height: parent.height * 0.32; border-radius: self.height / 2; background: #8899aa; } Rectangle { x: parent.width * 0.20; y: parent.height * 0.0 + bob; width: parent.width * 0.32; height: parent.height * 0.30; border-radius: self.height / 2; background: #8899aa; } Rectangle { x: parent.width * 0.42; y: parent.height * -0.04 + bob; width: parent.width * 0.34; height: parent.height * 0.32; border-radius: self.height / 2; background: #8899aa; } // Animated drops Rectangle { x: parent.width * 0.22; y: parent.height * 0.50; width: 2px; height: 14px; border-radius: 1px; background: #aaddff; opacity: Math.max(0.0, Math.sin((tick * 3.0) * 1rad)) * 0.7; } Rectangle { x: parent.width * 0.38; y: parent.height * 0.55; width: 2px; height: 12px; border-radius: 1px; background: #aaddff; opacity: Math.max(0.0, Math.sin((tick * 3.2 + 1.5) * 1rad)) * 0.7; } Rectangle { x: parent.width * 0.54; y: parent.height * 0.48; width: 2px; height: 14px; border-radius: 1px; background: #aaddff; opacity: Math.max(0.0, Math.sin((tick * 2.8 + 3.0) * 1rad)) * 0.7; } Rectangle { x: parent.width * 0.70; y: parent.height * 0.53; width: 2px; height: 12px; border-radius: 1px; background: #aaddff; opacity: Math.max(0.0, Math.sin((tick * 3.5 + 2.0) * 1rad)) * 0.6; } Rectangle { x: parent.width * 0.30; y: parent.height * 0.68; width: 2px; height: 10px; border-radius: 1px; background: #aaddff; opacity: Math.max(0.0, Math.sin((tick * 3.1 + 4.0) * 1rad)) * 0.5; } Rectangle { x: parent.width * 0.62; y: parent.height * 0.70; width: 2px; height: 10px; border-radius: 1px; background: #aaddff; opacity: Math.max(0.0, Math.sin((tick * 2.9 + 5.5) * 1rad)) * 0.5; } } // ═══ UI Components ══════════════════════════════════════════ // ─── Forecast Card (glassmorphism) ────────────────────────── component ForecastCard inherits Rectangle { in property hour; in property temp; in property icon-type; in property is-now: false; in property tick; width: 64px; height: 110px; border-radius: 18px; background: is-now ? #ffffff22 : #ffffff0c; border-width: 1px; border-color: is-now ? #ffffff30 : #ffffff0a; drop-shadow-blur: is-now ? 12px : 0px; drop-shadow-color: #ffffff11; animate background { duration: 400ms; easing: ease-in-out; } animate border-color { duration: 400ms; easing: ease-in-out; } animate drop-shadow-blur { duration: 400ms; easing: ease-in-out; } VerticalLayout { alignment: center; spacing: 6px; padding-top: 10px; padding-bottom: 10px; padding-left: 6px; padding-right: 6px; Text { text: hour; color: is-now ? #ffffffee : #ffffff88; font-size: 11px; font-weight: is-now ? 600 : 400; horizontal-alignment: center; } // Mini icon area Rectangle { height: 30px; if icon-type == 0: Rectangle { width: 20px; height: 20px; x: (parent.width - self.width) / 2; y: 5px; border-radius: 10px; background: @radial-gradient(circle, #FFE066 0%, #FFA500 100%); drop-shadow-blur: 6px; drop-shadow-color: #FFD70055; } if icon-type == 1: Rectangle { x: (parent.width - 28px) / 2; y: 4px; width: 28px; height: 18px; background: transparent; Rectangle { x: 2px; y: 8px; width: 24px; height: 10px; border-radius: 5px; background: #ffffffbb; } Rectangle { x: 6px; y: 2px; width: 14px; height: 12px; border-radius: 6px; background: #ffffffbb; } } if icon-type == 2: Rectangle { x: (parent.width - 28px) / 2; y: 2px; width: 28px; height: 24px; background: transparent; Rectangle { x: 2px; y: 4px; width: 24px; height: 10px; border-radius: 5px; background: #8899aacc; } Rectangle { x: 6px; y: 0px; width: 14px; height: 10px; border-radius: 5px; background: #8899aacc; } Rectangle { x: 8px; y: 16px; width: 1.5px; height: 6px; border-radius: 0.75px; background: #aaddff88; } Rectangle { x: 16px; y: 17px; width: 1.5px; height: 5px; border-radius: 0.75px; background: #aaddff88; } } } Text { text: "\{temp}°"; color: white; font-size: 15px; font-weight: 700; horizontal-alignment: center; } } } // ─── Info Pill (glassmorphism + accent) ───────────────────── component InfoPill inherits Rectangle { in property label; in property value; in property accent: #66bbff; horizontal-stretch: 1; height: 72px; border-radius: 18px; background: #ffffff0c; border-width: 1px; border-color: #ffffff0a; VerticalLayout { alignment: center; spacing: 4px; padding: 10px; // Accent dot HorizontalLayout { alignment: center; Rectangle { width: 6px; height: 6px; border-radius: 3px; background: accent; drop-shadow-blur: 4px; drop-shadow-color: accent; } } Text { text: value; color: white; font-size: 18px; font-weight: 600; horizontal-alignment: center; } Text { text: label; color: #ffffff55; font-size: 9px; horizontal-alignment: center; letter-spacing: 1.2px; } } } // ─── Premium Slider ───────────────────────────────────────── component PremiumSlider inherits Rectangle { in-out property value: 12; in property minimum: 0; in property maximum: 24; height: 44px; background: transparent; property ratio: Math.clamp((value - minimum) / (maximum - minimum), 0, 1); // Track Rectangle { y: (parent.height - 4px) / 2; width: parent.width; height: 4px; border-radius: 2px; background: #ffffff12; } // Fill Rectangle { y: (parent.height - 4px) / 2; width: parent.width * ratio; height: 4px; border-radius: 2px; background: @linear-gradient(90deg, #ffffff20, #ffffff88); } // Thumb glow Rectangle { x: (parent.width - 28px) * ratio; y: (parent.height - 28px) / 2; width: 28px; height: 28px; border-radius: 14px; background: #ffffff08; drop-shadow-blur: 16px; drop-shadow-color: #ffffff22; } // Thumb Rectangle { x: (parent.width - 22px) * ratio; y: (parent.height - 22px) / 2; width: 22px; height: 22px; border-radius: 11px; background: @radial-gradient(circle, #ffffff 0%, #e8e8f0 100%); drop-shadow-blur: 8px; drop-shadow-color: #00000022; Rectangle { width: 6px; height: 6px; x: 8px; y: 8px; border-radius: 3px; background: #888899; } } // Touch TouchArea { clicked => { root.value = root.minimum + (root.maximum - root.minimum) * Math.clamp(self.mouse-x / root.width, 0, 1); } moved => { if self.pressed { root.value = root.minimum + (root.maximum - root.minimum) * Math.clamp(self.mouse-x / root.width, 0, 1); } } } } // ─── Separator ────────────────────────────────────────────── component Separator inherits HorizontalLayout { alignment: center; Rectangle { width: 40px; height: 1px; border-radius: 0.5px; background: @linear-gradient(90deg, #ffffff00, #ffffff22, #ffffff00); } } // ═════════════════════════════════════════════════════════════ // Main Window // ═════════════════════════════════════════════════════════════ export component MainWindow inherits Window { title: "Meteo Milano"; preferred-width: 420px; preferred-height: 780px; background: black; in-out property tick: 0; in-out property time-of-day: 14.0; in-out property sky-top: #1e90ff; in-out property sky-bottom: #87CEEB; in-out property current-temp: 22; in-out property weather-desc: "Parzialmente Nuvoloso"; in-out property humidity: "65%"; in-out property wind: "12 km/h"; in-out property feels-like: "20°"; in-out property weather-type: 1; in-out property sun-y-factor: 0.3; in-out property <[HourData]> forecast: []; in-out property show-rain: false; callback open-settings; // ── Background ── Rectangle { width: 100%; height: 100%; background: @linear-gradient(180deg, sky-top, sky-bottom); } // ── Sky Sun ── Rectangle { property s: 90px; x: root.width * 0.70; y: root.height * root.sun-y-factor; width: s; height: s; border-radius: s / 2; background: @radial-gradient(circle, #FFD700 0%, #FF8C0060 60%, #FF450000 100%); opacity: weather-type == 0 ? 0.95 : weather-type == 1 ? 0.35 : 0.0; drop-shadow-blur: 40px + Math.sin(tick * 2rad) * 10px; drop-shadow-color: #FFD70055; animate opacity { duration: 600ms; easing: ease-in-out; } Rectangle { width: parent.width * 0.35; height: parent.height * 0.35; x: parent.width * 0.325; y: parent.height * 0.325; border-radius: self.width / 2; background: white; opacity: 0.35 + Math.sin(tick * 3rad) * 0.2; } } // ── Moon ── Rectangle { property m: 48px; x: root.width * 0.76; y: root.height * 0.10; width: m; height: m; border-radius: m / 2; background: #E8E8D0; opacity: time-of-day < 5.5 || time-of-day > 20.5 ? 0.85 : 0.0; drop-shadow-blur: 18px; drop-shadow-color: #E8E8D044; animate opacity { duration: 800ms; easing: ease-in-out; } Rectangle { width: parent.width * 0.7; height: parent.height * 0.7; x: parent.width * 0.38; y: parent.height * 0.08; border-radius: self.width / 2; background: sky-top; } } // ── Floating clouds ── CloudShape { width: 150px; height: 75px; x: root.width * 0.02 + Math.sin((tick * 0.2) * 1rad) * 25px; y: root.height * 0.05; tick: root.tick; speed: 0.3; phase: 0; base-opacity: 0.20; } CloudShape { width: 115px; height: 58px; x: root.width * 0.52 + Math.sin((tick * 0.15 + 2.0) * 1rad) * 20px; y: root.height * 0.12; tick: root.tick; speed: 0.25; phase: 2.0; base-opacity: 0.16; } CloudShape { width: 100px; height: 50px; x: root.width * 0.25 + Math.sin((tick * 0.18 + 4.5) * 1rad) * 30px; y: root.height * 0.19; tick: root.tick; speed: 0.35; phase: 4.5; base-opacity: 0.13; } // ── Rain ── if show-rain: Rectangle { width: 100%; height: 100%; RainDrop { x: root.width * 0.06; y: root.height * 0.28; tick: root.tick; speed: 3.2; phase: 0.0; } RainDrop { x: root.width * 0.13; y: root.height * 0.40; tick: root.tick; speed: 2.8; phase: 0.7; } RainDrop { x: root.width * 0.20; y: root.height * 0.34; tick: root.tick; speed: 3.5; phase: 1.4; } RainDrop { x: root.width * 0.28; y: root.height * 0.48; tick: root.tick; speed: 3.0; phase: 2.1; } RainDrop { x: root.width * 0.35; y: root.height * 0.37; tick: root.tick; speed: 3.3; phase: 2.8; } RainDrop { x: root.width * 0.42; y: root.height * 0.44; tick: root.tick; speed: 2.9; phase: 3.5; } RainDrop { x: root.width * 0.50; y: root.height * 0.31; tick: root.tick; speed: 3.4; phase: 4.2; } RainDrop { x: root.width * 0.57; y: root.height * 0.46; tick: root.tick; speed: 3.1; phase: 4.9; } RainDrop { x: root.width * 0.64; y: root.height * 0.39; tick: root.tick; speed: 2.7; phase: 5.6; } RainDrop { x: root.width * 0.72; y: root.height * 0.50; tick: root.tick; speed: 3.6; phase: 6.3; } RainDrop { x: root.width * 0.80; y: root.height * 0.35; tick: root.tick; speed: 3.0; phase: 7.0; } RainDrop { x: root.width * 0.88; y: root.height * 0.42; tick: root.tick; speed: 3.3; phase: 7.7; } RainDrop { x: root.width * 0.09; y: root.height * 0.54; tick: root.tick; speed: 2.8; phase: 8.4; } RainDrop { x: root.width * 0.38; y: root.height * 0.57; tick: root.tick; speed: 3.1; phase: 9.8; } RainDrop { x: root.width * 0.68; y: root.height * 0.55; tick: root.tick; speed: 3.4; phase: 11.2; } RainDrop { x: root.width * 0.84; y: root.height * 0.60; tick: root.tick; speed: 3.2; phase: 11.9; } RainDrop { x: root.width * 0.17; y: root.height * 0.63; tick: root.tick; speed: 2.6; phase: 1.1; } RainDrop { x: root.width * 0.46; y: root.height * 0.66; tick: root.tick; speed: 3.7; phase: 3.3; } RainDrop { x: root.width * 0.76; y: root.height * 0.62; tick: root.tick; speed: 2.9; phase: 5.0; } RainDrop { x: root.width * 0.93; y: root.height * 0.52; tick: root.tick; speed: 3.1; phase: 8.8; } } // ── Main content ── VerticalLayout { padding-top: 48px; padding-bottom: 24px; padding-left: 30px; padding-right: 30px; spacing: 2px; // City name Text { text: "MILANO"; color: white; font-size: 16px; font-weight: 600; letter-spacing: 6px; horizontal-alignment: center; } Text { text: "Sabato 31 Gennaio"; color: #ffffff77; font-size: 12px; font-weight: 400; horizontal-alignment: center; } Rectangle { vertical-stretch: 1; } // Central weather icon HorizontalLayout { alignment: center; if weather-type == 0: SunDisplay { width: 100px; height: 100px; tick: root.tick; } if weather-type == 1: CloudDisplay { width: 110px; height: 70px; tick: root.tick; } if weather-type == 2: RainDisplay { width: 110px; height: 80px; tick: root.tick; } } Rectangle { height: 8px; } // Temperature Text { text: "\{current-temp}°"; color: white; font-size: 92px; font-weight: 200; horizontal-alignment: center; } Text { text: weather-desc; color: #ffffffbb; font-size: 16px; font-weight: 400; horizontal-alignment: center; } Rectangle { height: 16px; } Separator { } Rectangle { height: 16px; } // Info pills HorizontalLayout { spacing: 10px; InfoPill { label: "UMIDITA"; value: humidity; accent: #66bbff; } InfoPill { label: "VENTO"; value: wind; accent: #88ddaa; } InfoPill { label: "PERCEPITA"; value: feels-like; accent: #ffaa66; } } Rectangle { height: 20px; } // Forecast section Text { text: "PREVISIONI ORARIE"; color: #ffffff55; font-size: 10px; font-weight: 500; letter-spacing: 2px; } Rectangle { height: 8px; } HorizontalLayout { spacing: 7px; for data in forecast: ForecastCard { hour: data.hour; temp: data.temp; icon-type: data.icon-type; is-now: data.is-now; tick: root.tick; } } Rectangle { vertical-stretch: 2; } // Time control Text { text: "Scorri per cambiare l'ora"; color: #ffffff33; font-size: 10px; font-weight: 400; horizontal-alignment: center; } Rectangle { height: 4px; } PremiumSlider { minimum: 0; maximum: 23.9; value <=> root.time-of-day; } HorizontalLayout { Text { text: "00:00"; color: #ffffff33; font-size: 9px; font-weight: 400; } Rectangle { horizontal-stretch: 1; } Text { text: "12:00"; color: #ffffff33; font-size: 9px; font-weight: 400; horizontal-alignment: center; } Rectangle { horizontal-stretch: 1; } Text { text: "24:00"; color: #ffffff33; font-size: 9px; font-weight: 400; horizontal-alignment: right; } } } // ── Settings button (top-right) ── Rectangle { x: root.width - 52px; y: 12px; width: 40px; height: 40px; border-radius: 20px; background: #ffffff12; border-width: 1px; border-color: #ffffff1a; // Gear icon (simplified) Rectangle { width: 20px; height: 20px; x: 10px; y: 10px; Rectangle { width: 12px; height: 12px; x: 4px; y: 4px; border-radius: 6px; background: transparent; border-width: 2px; border-color: #ffffffaa; } Rectangle { width: 3px; height: 6px; x: 8.5px; y: 0px; border-radius: 1.5px; background: #ffffffaa; } Rectangle { width: 3px; height: 6px; x: 8.5px; y: 14px; border-radius: 1.5px; background: #ffffffaa; } Rectangle { width: 6px; height: 3px; x: 0px; y: 8.5px; border-radius: 1.5px; background: #ffffffaa; } Rectangle { width: 6px; height: 3px; x: 14px; y: 8.5px; border-radius: 1.5px; background: #ffffffaa; } } TouchArea { clicked => { root.open-settings(); } } } }