/* ============================================================
Hero — home
Two modes, switchable from the Tweaks panel ("AIR-скролл"):
· classic — the original single-screen composition: giant
flanking А|Л, frosted chart, scroll affordance. The letters
collapse into the top-centre monogram as you scroll out.
· air — the aircenter.space-style PINNED scroll act. The chart
background pins for the whole section while the content rides
over it (parallax). A rotating tagline cycles through three
statements, driven directly by scroll, with a numeric counter
(01 / 03) + progress bar. A "+" card opens a detail popup on
hover. The giant А|Л travels up and collapses across the pin.
Reduced-motion always falls back to the classic single screen.
============================================================ */
const easeInOut = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);
const lerp = (a, b, t) => a + (b - a) * t;
const clamp01 = (v) => Math.min(1, Math.max(0, v));
/* fixed, scroll-linked А|Л that collapses into a top-CENTER monogram */
function MorphLogo({ progress, dims, onHome, dark }) {
const { W, H } = dims;
const e = easeInOut(progress);
// white while the logo sits over a dark area (hero / Results), black over the white sections
const letterColor = dark ? "var(--paper-on-ink)" : "var(--ink)";
const mob = W < 700;
// start (hero, giant flanking letters) ⇢ end (small centred А|Л monogram, top centre)
const startFS = Math.max(88, Math.min(mob ? 0.30 * W : 0.18 * W, 0.42 * H, 260));
const endFS = mob ? 24 : 28;
const fs = lerp(startFS, endFS, e);
const xC = W / 2;
const gap = mob ? 18 : 21; // half-distance between А and Л centres when collapsed
const yEnd = 36;
const yA = lerp(0.5 * H, yEnd, e);
const xA = lerp((mob ? 0.18 : 0.086) * W, xC - gap, e);
const xL = lerp((mob ? 0.82 : 0.9) * W, xC + gap, e);
const track = lerp(0.02, -0.01, e);
const letter = (ch, x) => ({
position: "fixed", left: x, top: yA, transform: "translate(-50%, -50%)",
fontFamily: "var(--font-display)", fontWeight: 300, fontSize: fs,
lineHeight: 0.8, letterSpacing: track + "em", color: letterColor,
pointerEvents: "none", zIndex: 45, willChange: "left, top, font-size",
});
const collapsed = progress > 0.92;
return (
{/* А|Л text monogram — letterColor already flips to white over dark sections,
so the collapsed mark stays the same size on black as on paper. */}
А
{/* thin separator bar — fades in only as the monogram forms */}
Л
{/* invisible hit-area over the collapsed monogram → back to top */}
);
}
/* ---- scroll affordance with a rotating arrow (AIR btn--rotation) ---- */
function RotScroll({ onClick, light }) {
const [hover, setHover] = React.useState(false);
const stroke = light ? "var(--ink)" : "var(--paper-on-ink)";
const border = light ? "var(--hairline-2)" : "rgba(250,250,248,0.28)";
return (
);
}
/* ---- hover-expand card (AIR card-team popup) ---- */
function HoverPanelCard({ label, items, light }) {
const [open, setOpen] = React.useState(false);
const ink = light;
const surface = ink ? "rgba(255,255,255,0.72)" : "rgba(16,17,19,0.42)";
const txt = ink ? "var(--ink)" : "var(--paper-on-ink)";
const sub = ink ? "var(--ink-2)" : "rgba(250,250,248,0.74)";
const line = ink ? "var(--hairline-2)" : "rgba(250,250,248,0.18)";
return (
{morph}
);
}
/* ---------------- AIR pinned scroll act ---------------- */
const roll = t("home.heroRoll") || [];
const N = Math.max(1, roll.length);
// The first stretch of the pin is a clean "classic" screen — just the giant
// А|Л letters over the chart. The AIR roll (taglines, counter, progress bar,
// hover card) fades in only once the user starts scrolling.
const INTRO = 0.16;
const airIn = easeInOut(clamp01(p / INTRO)); // 0 at rest → 1 once scrolling
const q = clamp01((p - INTRO) / (1 - INTRO)); // roll progress after the intro
const fp = q * (N - 1);
const activeIdx = Math.min(N - 1, Math.max(0, Math.round(fp)));
const lineColor = light ? "var(--ink)" : "var(--paper-on-ink)";
const eyeColor = light ? "var(--ink-3)" : "rgba(250,250,248,0.62)";
const barTrack = light ? "var(--hairline-2)" : "rgba(250,250,248,0.22)";
const barFill = light ? "var(--ink)" : "var(--paper-on-ink)";
const arrowFade = clamp01(1 - p * 2.4); // affordance retreats as you ride the pin
// chart parallax — recedes & lifts a touch so the content rides over it
const chartShift = { transform: `translateY(${-p * 7}vh) scale(${1 + p * 0.06})`, transformOrigin: "50% 40%",
opacity: 1 - p * 0.18, transition: "none", willChange: "transform" };
const stageItems = t("home.m2.rows");
const cardItems = Array.isArray(stageItems)
? stageItems.slice(0, 4).map((r) => r.title)
: ["Чтение рынка", "Smart Money", "Риск-менеджмент"];
return (
{/* pinned viewport-height layer — everything rides on top of the chart */}
{/* parallax background */}
{/* readability veil that deepens slightly as content takes over */}
{/* rotating tagline stack — scroll-driven crossfade */}
{roll.map((m, i) => {
const d = fp - i; // <0 below (incoming), >0 above (outgoing)
// each label fully fades out before the next begins: visible only within
// |d| < 0.46, so adjacent labels never overlap (one clears, then the next arrives)
const op = easeInOut(clamp01(1 - Math.abs(d) / 0.46)) * airIn;
const ty = -d * 42;
return (