diff --git a/landing/app.js b/landing/app.js index fd59d58..fd217ba 100644 --- a/landing/app.js +++ b/landing/app.js @@ -244,7 +244,10 @@ function scrollPhase() { const doc = document.documentElement const max = doc.scrollHeight - window.innerHeight - return clamp(max > 0 ? window.scrollY / max : 0, 0, 1) + const x = clamp(max > 0 ? window.scrollY / max : 0, 0, 1) + // Acelera la transición luz→noche: la fase avanza más deprisa hacia el final + // (el atardecer llega antes, la noche se alcanza antes de llegar abajo del todo). + return 1 - Math.pow(1 - x, 1.3) } /* ── Gráfica del día ── */ @@ -409,6 +412,15 @@ /* ── Batería ── */ const ringFg = $('#ringFg'), batterySoc = $('#batterySoc'), batteryStatus = $('#batteryStatus') + const batTempVal = $('#batTempVal'), batAutonomyVal = $('#batAutonomyVal') + const BATTERY_CAPACITY_KWH = 10 + const fmtAutonomy = (hours) => { + if (hours >= 100) return '∞' + if (hours < 1) return `${Math.max(1, Math.round(hours * 60))} min` + const h = Math.floor(hours), m = Math.round((hours - h) * 60) + if (m >= 60) return `${h + 1} h` + return m ? `${h} h ${m} min` : `${h} h` + } let curSoc = 100 function updateBattery(h) { const st = batteryState(h) @@ -416,6 +428,14 @@ ringFg.style.strokeDashoffset = String(314.16 * (1 - curSoc / 100)) batterySoc.textContent = String(Math.round(curSoc)) batteryStatus.textContent = MICRO[document.documentElement.lang || 'es'][st.mode] + const temp = 23 + (st.mode === 'charge' ? 2.5 : 0) + (st.mode === 'discharge' ? 0.9 : 0) + Math.sin(h * 1.7) * 0.4 + batTempVal.textContent = `${temp.toFixed(1)} °C` + if (st.mode === 'charge') { + batAutonomyVal.textContent = '…' + } else { + const cons = Math.max(0.1, consumptionCurve(h)) + batAutonomyVal.textContent = fmtAutonomy((curSoc / 100) * BATTERY_CAPACITY_KWH / cons) + } } /* ── Flujo de energía (réplica del widget de la app) ── */ @@ -590,6 +610,39 @@ els.forEach((el) => io.observe(el)) } + /* ── Teatro: animación al hacer scroll ── */ + const theaterEl = document.querySelector('.theater') + const theaterStagger = [ + ...document.querySelectorAll('.theater-eyebrow, .theater-title, .theater-sub'), + ...document.querySelectorAll('.theater-widgets .widget'), + ] + const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + function theaterScroll() { + if (reduceMotion) return + const vh = window.innerHeight + const top = theaterEl.getBoundingClientRect().top + window.scrollY + const start = top - vh + const total = vh + vh * 1.4 + const p = clamp((window.scrollY - start) / total, 0, 1) + let i = 0 + for (const el of theaterStagger) { + const w0 = (i === 0 ? 0 : 0.06 + (i - 1) * 0.11) + const w1 = w0 + 0.12 + const e = ss(w0, w1, p) + el.style.opacity = e.toFixed(3) + el.style.transform = e >= 1 ? '' : `translateY(${((1 - e) * 18).toFixed(1)}px)` + i++ + } + } + function theaterScrollInit() { + if (reduceMotion) return + for (const el of theaterStagger) { + el.style.opacity = '0' + el.style.transform = 'translateY(18px)' + } + theaterScroll() + } + /* ── Init ── */ function init() { const i18n = window.heliosI18n @@ -606,6 +659,7 @@ setInterval(buildTicker, 10000) setupSlider() setupReveal() + theaterScrollInit() updateFlow(hourNow()) let lastDom = 0 @@ -621,6 +675,7 @@ function frame(ts) { const h = hourNow() drawSky(scrollPhase(), h) + theaterScroll() const heroChart = $('#heroChart'), theaterChart = $('#theaterChart'), theaterHist = $('#theaterHist') if (chartNeedsRedraw(heroChart)) drawDayChart(heroChart, true) if (chartNeedsRedraw(theaterChart)) drawDayChart(theaterChart, false) diff --git a/landing/i18n.js b/landing/i18n.js index 4e722b9..a267a4d 100644 --- a/landing/i18n.js +++ b/landing/i18n.js @@ -42,7 +42,7 @@ const I18N = { 'how.s2d': 'Un único servicio Node.js en casa lee por WebSocket los sensores que definas en una topología configurable: N inversores, batería opcional, grid por sensores o scraper.', 'how.s3t': 'Míralo donde quieras', 'how.s3d': 'La app es una PWA responsive: instálala, entra con tu usuario y revisa tu día, tu histórico y tu ahorro desde cualquier pantalla.', - 'how.thanks': 'Gracias, Home Assistant. 💛', + 'how.thanks': 'Gracias, Home Assistant. ❤️', 'how.thanksD': 'Helios no sería posible sin el proyecto Home Assistant y su comunidad. Se conecta a tu HAOS local por WebSocket, sin tocar su API pública, y lee solo los sensores que tú decides.', 'theater.eyebrow': 'LA APP EN MOVIMIENTO', 'theater.t1': 'Mírala respirar.', @@ -55,6 +55,8 @@ const I18N = { 'theater.lCons': 'Consumo', 'theater.bCharged': 'Cargada hoy', 'theater.bDischarged': 'Descargada hoy', + 'theater.bTemp': 'Temperatura', + 'theater.bAutonomy': 'Autonomía', 'theater.bCycles': 'Ciclos', 'theater.bHealth': 'Salud', 'flow.fv': 'Fotovoltaica', @@ -62,7 +64,7 @@ const I18N = { 'flow.battery': 'Batería', 'flow.grid': 'Red', 'screens.eyebrow': 'LA APP', - 'screens.title': 'Hecha para mirarse todos los días.', + 'screens.title': 'Hecha para mirarla todos los días.', 'slide1': 'Vista Hoy · curva en vivo, flujo y totales del día', 'slide2': 'Batería · SOC y curva de carga y descarga', 'slide3': 'Histórico · cientos de días de tu instalación', @@ -147,7 +149,7 @@ const I18N = { 'how.s2d': 'A single Node.js service at home reads over WebSocket the sensors you define in a configurable topology: N inverters, optional battery, grid via sensors or scraper.', 'how.s3t': 'Watch it anywhere', 'how.s3d': 'The app is a responsive PWA: install it, sign in and check your day, your history and your savings from any screen.', - 'how.thanks': 'Thank you, Home Assistant. 💛', + 'how.thanks': 'Thank you, Home Assistant. ❤️', 'how.thanksD': 'Helios would not exist without the Home Assistant project and its community. It connects to your local HAOS over WebSocket, never touches its public API, and reads only the sensors you choose.', 'theater.eyebrow': 'THE APP IN MOTION', 'theater.t1': 'Watch it breathe.', @@ -160,6 +162,8 @@ const I18N = { 'theater.lCons': 'Consumption', 'theater.bCharged': 'Charged today', 'theater.bDischarged': 'Discharged today', + 'theater.bTemp': 'Temperature', + 'theater.bAutonomy': 'Autonomy', 'theater.bCycles': 'Cycles', 'theater.bHealth': 'Health', 'flow.fv': 'Photovoltaic', diff --git a/landing/index.html b/landing/index.html index 2769451..efce8e3 100644 --- a/landing/index.html +++ b/landing/index.html @@ -25,9 +25,9 @@ - - - + + + @@ -209,6 +209,14 @@

Mírala respirar.

Descargada hoy 0.0 kWh +
+ Temperatura + 24.0 °C +
+
+ Autonomía + … +
Ciclos 312 @@ -325,7 +333,7 @@

Mírala respirar.

LA APP

-

Hecha para mirarse todos los días.

+

Hecha para mirarla todos los días.

@@ -356,37 +364,19 @@

Hecha para mirarse todos los

- -
-
-

PREGUNTAS FRECUENTES

-

Lo que todo el mundo pregunta.

+ +
+

El sol no espera.
Tu instalación tampoco.

+ -
-
- ¿Escribe datos en mi HAOS? -

No. Solo lectura: ni una configuración, ni un dato, ni una escena. Helios se suscribe a tus sensores y lee estadísticas del grabador.

-
-
- ¿Necesita cuenta, nube o registrarme? -

No. Ni cuenta, ni nube, ni datos fuera de casa. Se instala en tu propia máquina y es 100 % AGPL-3.0: puedes leerlo, auditarlo y forkearlo.

-
-
- ¿Sirve para cualquier instalación? -

Desde 0.7.x la topología es configurable: N inversores, batería opcional y grid por sensores o por atributos de un scraper. Una instalación nueva requiere editar un JSON con tus sensores; no hay aún editor visual de topología.

-
-
- ¿Cuánto consume? -

Un proceso Node.js pequeño con SQLite. Cabe en un LXC sin estrenar: la demo completa, con 953 días de histórico, va sobrada en 2 GB de RAM.

-
-
- ¿Y si se cae internet? -

Tu casa sigue funcionando: el sol no espera. Helios es local y sigue pintando tu instalación aunque la nube de nadie esté caída.

-
-
- ¿Funciona en el móvil? -

Sí: es una PWA instalable, responsive, con temas claro y oscuro, y acceso multiusuario con perfiles e idiomas.

-
+
+

Si algo de lo que ves te ha sido útil, me harías muy feliz con un café en Ko-fi. No vivo de esto: es mi hobby, hecho en casa y para compartir. Pero si el proyecto recibe apoyo, podré dedicarle más tiempo. Y de paso me dices si te gusta la curva.

+ + + Apóyame en Ko-fi +
@@ -425,42 +415,59 @@

De personas, no de empresas

- -
-

El sol no espera.
Tu instalación tampoco.

-
- Abrir la demo - Fork en GitHub + +
+
+

PREGUNTAS FRECUENTES

+

Lo que todo el mundo pregunta.

-
-

Si algo de lo que ves te ha sido útil, me harías muy feliz con un café en Ko-fi. No vivo de esto: es mi hobby, hecho en casa y para compartir. Pero si el proyecto recibe apoyo, podré dedicarle más tiempo. Y de paso me dices si te gusta la curva.

- - - Apóyame en Ko-fi - +
+
+ ¿Escribe datos en mi HAOS? +

No. Solo lectura: ni una configuración, ni un dato, ni una escena. Helios se suscribe a tus sensores y lee estadísticas del grabador.

+
+
+ ¿Necesita cuenta, nube o registrarme? +

No. Ni cuenta, ni nube, ni datos fuera de casa. Se instala en tu propia máquina y es 100 % AGPL-3.0: puedes leerlo, auditarlo y forkearlo.

+
+
+ ¿Sirve para cualquier instalación? +

Desde 0.7.x la topología es configurable: N inversores, batería opcional y grid por sensores o por atributos de un scraper. Una instalación nueva requiere editar un JSON con tus sensores; no hay aún editor visual de topología.

+
+
+ ¿Cuánto consume? +

Un proceso Node.js pequeño con SQLite. Cabe en un LXC sin estrenar: la demo completa, con 953 días de histórico, va sobrada en 2 GB de RAM.

+
+
+ ¿Y si se cae internet? +

Tu casa sigue funcionando: el sol no espera. Helios es local y sigue pintando tu instalación aunque la nube de nadie esté caída.

+
+
+ ¿Funciona en el móvil? +

Sí: es una PWA instalable, responsive, con temas claro y oscuro, y acceso multiusuario con perfiles e idiomas.

+
- - -
-
-

Gracias, Home Assistant. 💛

-

Helios no sería posible sin el proyecto Home Assistant y su comunidad. Se conecta a tu HAOS local por WebSocket, sin tocar su API pública, y lee solo los sensores que tú decides.

- home-assistant.io → + -
+ + +
+
+

Gracias, Home Assistant. ❤️

+

Helios no sería posible sin el proyecto Home Assistant y su comunidad. Se conecta a tu HAOS local por WebSocket, sin tocar su API pública, y lee solo los sensores que tú decides.

+ home-assistant.io → +
+
+