/* Landing page component — Rubin */
/* global React, window */
const { useState, useEffect } = React;
// Scroll-reveal: attach `.in` to any `.reveal` element as it enters the viewport.
function useScrollReveal() {
useEffect(() => {
const els = document.querySelectorAll('.reveal');
if (!els.length) return;
if (!('IntersectionObserver' in window)) {
els.forEach(el => el.classList.add('in'));
return;
}
const io = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('in');
io.unobserve(e.target);
}
});
}, { rootMargin: '0px 0px -12% 0px', threshold: 0.12 });
els.forEach(el => io.observe(el));
return () => io.disconnect();
}, []);
}
function Landing() {
const tweaks = window.useTweaks
? window.useTweaks(window.TWEAK_DEFAULTS)
: [window.TWEAK_DEFAULTS, () => {}];
const [t, setT] = tweaks;
const lang = (t && t.lang) || 'en';
const c = window.LANDING_COPY[lang];
const setLang = l => setT('lang', l);
useScrollReveal();
return (
{window.TweaksPanel_App ?
: null}
);
}
// ─── Nav ────────────────────────────────────────────────────────────
function Nav({ c, lang, setLang }) {
return (
);
}
// ─── Hero ───────────────────────────────────────────────────────────
function Hero({ c }) {
const [a, b, em, rest] = c.hero.title;
// Compute live stats from window.TRAINS if available
const trains = (window.TRAINS || []).filter(x => x.status === 'enroute');
const totalRev = trains.reduce((s, x) => s + x.revenue, 0);
const totalCap = trains.reduce((s, x) => s + x.capacity, 0);
const totalSold = trains.reduce((s, x) => s + x.soldSeats, 0);
const loadPct = totalCap ? Math.round((totalSold / totalCap) * 100) : 0;
const revFmt = totalRev >= 1_000_000
? (totalRev / 1_000_000).toFixed(2) + ' mln zł'
: Math.round(totalRev / 1000) + ' tys. zł';
return (
{c.hero.eyebrow}
{a}
{b}{em}{rest}
{c.hero.sub}
RUBIN · LIVE FEED
PL · CZ · DE
Revenue · today
{revFmt}+4.8%
Load factor
{loadPct}%+3.1pp
Trains · live
{trains.length}
);
}
// ─── Ticker ─────────────────────────────────────────────────────────
function Ticker({ c }) {
const items = [...c.ticker, ...c.ticker]; // duplicate for seamless loop
return (
{items.map((it, i) => (
{it.k}
·
{it.v}
{it.n}
{it.d}
●
))}
);
}
// ─── Problem ────────────────────────────────────────────────────────
function Problem({ c }) {
const [a, em, rest] = c.problem.title;
return (
{c.problem.label.split(' / ')[0]} / {c.problem.label.split(' / ')[1]}
{a}{em}{rest}
{c.problem.sub}
{c.problem.cards.map((card, i) => (
{card.big[0]}{card.big[1]}
{card.t}
{card.d}
{card.src}
))}
);
}
// ─── Capabilities ───────────────────────────────────────────────────
function Capabilities({ c }) {
const [a, em, rest] = c.caps.title;
const vizMap = {
curve: ,
pricing: ,
compete: ,
matrix: ,
};
return (
{c.caps.label.split(' / ')[0]} / {c.caps.label.split(' / ')[1]}
{a}{em}{rest}
{c.caps.sub}
{c.caps.items.map((it, i) => (
{it.num} {it.tag}
{it.title}
{it.desc}
{it.bullets.map((b, j) => - {b}
)}
{vizMap[it.viz]}
))}
);
}
// ─── How ────────────────────────────────────────────────────────────
function HowItWorks({ c }) {
const [a, em, rest] = c.how.title;
return (
{c.how.label.split(' / ')[0]} / {c.how.label.split(' / ')[1]}
{a}{em}{rest}
{c.how.cols.map((col, i) => (
{col.step}
{col.t}
{col.p}
{col.sources.map((s, j) => {s})}
))}
);
}
// ─── Demo band ──────────────────────────────────────────────────────
function DemoBand({ c }) {
const [a, em, rest] = c.demo.title;
return (
{c.demo.label}
{a}{em}{rest}
{c.demo.sub}
{c.demo.sub2}
{c.demo.cta} →
rubin.rail · control room
);
}
// ─── Audiences ──────────────────────────────────────────────────────
function Audiences({ c }) {
const [a, em, rest] = c.audiences.title;
return (
{c.audiences.label}
{a}{em}{rest}
{c.audiences.ops.icon}
{c.audiences.ops.h}
{c.audiences.ops.pitch}
{c.audiences.ops.bullets.map((b, i) => - {b}
)}
{c.audiences.inv.icon}
{c.audiences.inv.h}
{c.audiences.inv.pitch}
{c.audiences.inv.bullets.map((b, i) => - {b}
)}
);
}
// ─── Metrics ────────────────────────────────────────────────────────
function Metrics({ c }) {
const [a, em, rest] = c.metrics.title;
return (
{c.metrics.label}
{a}{em}{rest}
{c.metrics.sub}
{c.metrics.items.map((m, i) => (
{m.v[0]}{m.v[1]}
{m.l}
{m.d}
))}
);
}
// ─── CTA ────────────────────────────────────────────────────────────
function CTA({ c }) {
return (
);
}
// ─── Footer ─────────────────────────────────────────────────────────
function Footer({ c }) {
return (
);
}
window.Landing = Landing;