// Scenio landing · the hero — brief §3/§3a/§3c @911cee4. The photograph is full-width the whole time; copy, CTAs and the bottom-centred search never move; only the film and the callouts animate. §3c: the move is the approved one-shot camera walk (scenio-walk.mp4 → walk/0001–0097.jpg — the white manor, 10.04 s) scrubbed by scroll on a canvas — frame = round(p / .90 × 96) through a damped follower (.14, initialised at mount); Frame B (0097) is in place from p .90; dwell .92 → 1. Callouts are DOM overlays; every anchor, lifetime and box rule lives in hero-callouts.js (window.SCENIO_HERO_CALLOUTS) — authored against the manor film, see hero-callouts.html.
(() => {
if (!window.SCENIO_INIT) return; // host guard (C4-m10): the bundle carries this file too; it runs only on its host page, which sets window.SCENIO_INIT first
const NS = window.ScenioPaperCrimson_b9a328;
const { UButton, UFormField, UInput, UInputNumber, USelectMenu, ScenioCallout, ScenioDate, ScenioLocationField, ScenioGuestField } = NS;
const { useState, useEffect, useRef, useLayoutEffect, useMemo, useCallback, memo, forwardRef } = React;
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
const seg = (p, a, b) => clamp((p - a) / (b - a), 0, 1);
const lerp = (a, b, t) => a + (b - a) * t;
const NAV = { desktop: 78, mobile: 66 };
const ASSETS = '../../assets/hero/';
const FRAME = { w: 1920, h: 1080 };                                        // the film's frame
const N = 97, FILM_END = .90;                                                // 97 frames scrubbed over p 0 → .90; Frame B holds from .90
const frameSrc = (i) => `${ASSETS}walk/${String(i + 1).padStart(4, '0')}.jpg`;
const CFG = window.SCENIO_HERO_CALLOUTS;                                     // hero-callouts.js — the authored set
const DWELL = .92, FOLLOW = .14, STAGGER = 120;
const LAUNCH = CFG.launch || [0, 420, 980];                                    // the launch cadence: the three A callouts arrive one after another with uneven gaps (ruling)
const WAVE_B = CFG.waves;                                                    // B wave 0 (Światło, Dekoracje) at p .7875 = frame 85, wave 1 (Muzyka) at .825 = frame 89 — the camera has settled
// Frame A set — anchors keyed per film frame ([frame, x%, y%], linear between keys); each callout has its own in / out frame and fades over `fade` frames before out; box = preferred top-left at the in frame, % of the film frame; the box then travels with its anchor
const SET_A = CFG.a;
// Frame B set — THREE on frame 0097: Światło → the wall lantern's flame, Muzyka → the open right leaf (abstract: “w salonie”), Dekoracje → the hydrangea urn; the boxes form one column on the wall right of the door, right edge 24px from the stage edge, tops at 40 / 47.5 / 55 % of the frame
const SET_B = CFG.b;
const DOOR_B = CFG.door.noGo;                                                // frame 0097: the door incl. leaves and casing (outer casing edge) — never covered by a box
const ALL = [...SET_A, ...SET_B];

// the film cover-fitted to the stage; a stage narrower than 16:9 crops from the LEFT (the landscape), so the venue, the doorway and the wall right of it stay in view at every size
function coverBox(W, H) { const k = Math.max(W / FRAME.w, H / FRAME.h); const w = FRAME.w * k, h = FRAME.h * k; return { w, h, k, x: -(w - W), y: -(h - H) / 2 }; }
const keyed = (keys, f) => { if (f <= keys[0][0]) return { x: keys[0][1], y: keys[0][2] }; for (let i = 1; i < keys.length; i++) { if (f <= keys[i][0]) { const t = (f - keys[i - 1][0]) / (keys[i][0] - keys[i - 1][0]); return { x: lerp(keys[i - 1][1], keys[i][1], t), y: lerp(keys[i - 1][2], keys[i][2], t) }; } } const a = keys[keys.length - 2], b = keys[keys.length - 1]; const t = Math.min((f - b[0]) / (b[0] - a[0]), 1.5); return { x: b[1] + (b[1] - a[1]) * t, y: b[2] + (b[2] - a[2]) * t }; };
// box estimate for the placer and the proofs: width = max(min, 22 + text) capped at max; the second line (the bound name, or the cta) wraps to two lines when it does not fit — height 30 (label only) · 44 (one line) · 59 (two lines)
const boxSize = (label, note, min = 112, max = Infinity, cta) => { const tw = (s) => (s ? 22 + String(s).length * 5.6 : 0); const w = Math.round(clamp(Math.max(22 + String(label).length * 8.4, tw(note), tw(cta), min), min, Math.max(min, max))); const lines = (s) => (s ? Math.min(2, Math.ceil((tw(s) - 22) / (w - 22))) : 0); const n = Math.max(lines(note), lines(cta)); return { w, h: n ? (n > 1 ? 59 : 44) : 30, lines: n }; };
// the Frame B column: right edge = W − right; boxes stack from their authored tops with `gap` between; the column shifts up when its bottom would cover an anchor standing in the column (the hydrangeas) or leave the stage
function columnB(defs, sizes, anchorsPx, W, H, topPx, right, gap = 8) { const sorted = defs.slice().sort((p, q) => p.top - q.top); const out = {}; let bottom = -Infinity; sorted.forEach((c) => { const y = Math.max(topPx(c.top), bottom + gap); out[c.id] = { x: W - right - sizes[c.id].w, y }; bottom = y + sizes[c.id].h; }); const top = out[sorted[0].id].y; const left = Math.min(...sorted.map((c) => out[c.id].x)); const inCol = sorted.map((c) => anchorsPx[c.id]).filter((a) => a && a.x >= left - 8); const below = inCol.filter((a) => a.y > (top + bottom) / 2); const limit = Math.min(H - 8, ...below.map((a) => a.y - 14)); const over = bottom - limit; if (over > 0) sorted.forEach((c) => { out[c.id].y -= over; }); return out; }
// the wall right of the door: how wide a Frame B box may be at this stage (never under the minimum)
const wallWidth = (W, box, right, min) => Math.max(min, W - right - (box.x + DOOR_B.right / 100 * box.w));
// boxes: preferred position, else shifts by the footprint + 8px, else the far side of the anchor, else the least-overlapping candidate — never over an anchor (±8px), another box, the copy, the search panel, the header band or (Frame B) the doorway. Obstacles carry their own margin m (default 8).
function placeSet(defs, prefer, anchorsPx, sizes, W, H, obstacles) {
  const PAD = 8, out = {}, placed = [];
  const inside = (r) => r.x >= PAD && r.y >= PAD && r.x + r.w <= W - PAD && r.y + r.h <= H - PAD;
  const hit = (r, o, m) => r.x < o.x + o.w + m && r.x + r.w + m > o.x && r.y < o.y + o.h + m && r.y + r.h + m > o.y;
  const coversAnchor = (r) => Object.values(anchorsPx).some((a) => a && a.x > r.x - PAD && a.x < r.x + r.w + PAD && a.y > r.y - PAD && a.y < r.y + r.h + PAD);
  const ok = (r) => inside(r) && !coversAnchor(r) && !placed.some((o) => hit(r, o, PAD)) && !obstacles.some((o) => hit(r, o, o.m ?? PAD));
  const overlap = (r) => [...placed, ...obstacles].reduce((s, o) => s + Math.max(0, Math.min(r.x + r.w, o.x + o.w) - Math.max(r.x, o.x)) * Math.max(0, Math.min(r.y + r.h, o.y + o.h) - Math.max(r.y, o.y)), 0) + (inside(r) ? 0 : 1e6);
  defs.forEach((c) => {
    const { w, h } = sizes[c.id]; const a = anchorsPx[c.id]; const pr = prefer[c.id]; if (!pr) return;
    const x0 = clamp(pr.x, PAD, Math.max(PAD, W - w - PAD)), y0 = clamp(pr.y, PAD, Math.max(PAD, H - h - PAD)); const dx = w + PAD, dy = h + PAD;
    const cands = [[x0, y0], [x0 - dx, y0], [x0 + dx, y0], [x0, y0 - dy], [x0, y0 + dy], [x0 - dx, y0 - dy], [x0 + dx, y0 - dy], [x0 - dx, y0 + dy], [x0 + dx, y0 + dy]];
    if (a) cands.push([2 * a.x - x0 - w, y0], [x0, 2 * a.y - y0 - h], [2 * a.x - x0 - w, 2 * a.y - y0 - h]);
    const rects = cands.map(([x, y]) => ({ x, y, w, h })); const pick = rects.find(ok) || rects.slice().sort((p, q) => overlap(p) - overlap(q))[0]; placed.push(pick); out[c.id] = pick;
  });
  return out;
}

// W1.7 (WCAG 2.2.2): the word turns through the list ONCE and rests on the first word; it never starts under reduced motion or with the film switched off (the still toggle)
function RotatingWord({ words, interval = 3600, reduced }) {
  const [i, setI] = useState(0);
  const [out, setOut] = useState(false);
  const [em, setEm] = useState([]);
  const box = useRef(null); const spans = useRef([]);
  const measure = () => { const fs = box.current ? parseFloat(getComputedStyle(box.current).fontSize) || 1 : 1; setEm(spans.current.map((el) => (el ? el.offsetWidth / fs : 0))); };
  useEffect(() => { measure(); const r = () => measure(); window.addEventListener('resize', r); if (document.fonts) document.fonts.ready.then(measure); return () => window.removeEventListener('resize', r); }, [words.join('|')]);
  useEffect(() => {
    if (reduced) return;
    let steps = 0;
    const t = setInterval(() => { steps += 1; if (steps > words.length) { clearInterval(t); return; } setOut(true); setTimeout(() => { setI((n) => (n + 1) % words.length); setOut(false); }, 300); if (steps === words.length) clearInterval(t); }, interval);
    return () => clearInterval(t);
  }, [words.length, interval, reduced]);
  return (
    <span ref={box} className="hero-word" style={{ width: em[i] ? `${em[i]}em` : 'auto' }}>
      <span className="hero-word-measure" aria-hidden="true">{words.map((w, k) => <span key={k} ref={(el) => { spans.current[k] = el; }} style={{ position: 'absolute', left: 0 }}>{w}</span>)}</span>
      <span key={i} className={'hero-word-cur ' + (out ? 'is-out' : 'is-in')}>{words[i]}</span>
    </span>
  );
}

// compact frames (< 1180): the callouts collapse to a legend row under the text — the category, then the bound name (or the listing cta); each row is the same link as its box; the Frame A set gives way to the Frame B set as the camera arrives
function Legend({ t, set, texts }) {
  const cur = set === 'a' ? SET_A : SET_B;
  return <ul className="hero-legend" aria-label={t.hero.legend}>{ALL.map((c) => { const x = texts[c.id]; return <li key={c.id} className={cur.includes(c) ? 'is-shown' : ''}><a href={x.href} title={x.name} aria-label={[x.label, x.name, x.cta].filter(Boolean).join(' — ')}><b>{x.label}</b><span>{x.name || x.cta}</span></a></li>; })}</ul>;
}

// the copy never moves: eyebrow · headline with the cycling word · one line · Start (crimson → register) · Odkrywaj (quiet outline → discovery)
const Copy = memo(forwardRef(function Copy({ t, reduced, still, legendSet, texts }, ref) {
  return (
    <div ref={ref} className="hero-copy">
      <p className="hero-eyebrow">{t.hero.eyebrow}</p>
      <h1 className="hero-title">{t.hero.before}<RotatingWord words={t.hero.words} interval={3600} reduced={reduced || still} /><br />{t.hero.after}</h1>
      <p className="hero-support">{t.hero.support}</p>
      <div className="hero-ctas">
        <UButton size="lg" color="primary" variant="solid" label={t.hero.ctaPrimary} to="/authentication?mode=register" />
        <UButton size="lg" color="neutral" variant="outline" className="hero-cta-quiet" label={t.hero.ctaSecondary} to="/discovery" />
      </div>
      {legendSet && <Legend t={t} set={legendSet} texts={texts} />}
    </div>
  );
}));

// the canon search panel — Gdzie (ScenioLocationField: country flag + autocomplete) · Kiedy (ScenioDate: exact ± days or flexible months) · Goście · Rodzaj · Szukaj — centred, top edge at 72 % of the viewport; one row on desktop, wrapping under 1024
const SearchPanel = memo(forwardRef(function SearchPanel({ t, locale = 'pl', size = 'md', className, style }, ref) {
  const s = t.hero.search;
  return (
    <form ref={ref} className={'hero-search ' + (className || '')} style={style} onSubmit={(e) => e.preventDefault()} aria-label={s.submit}>
      <UFormField label={s.where} name="where" size="sm"><ScenioLocationField locale={locale} size={size} /></UFormField>
      <UFormField label={s.when} name="when" size="sm"><ScenioDate locale={locale} size={size} /></UFormField>
      <UFormField label={s.guests} name="guests" size="sm"><ScenioGuestField engine="discovery" size={size} locale={locale} defaultValue={{ adults: 104, kids: 12, staff: 4 }} max={800} /></UFormField>
      <UFormField label={s.type} name="type" size="sm"><USelectMenu locale={locale} size={size} items={s.types} placeholder={s.typePh} searchInput={false} /></UFormField>
      <UButton className="hero-search-btn" size={size} type="submit" color="primary" variant="solid" icon="i-hugeicons-search-02" label={s.submit} />
    </form>
  );
}));

  // the film scrubber: a canvas painting walk/NNNN.jpg cover-fitted (crop from the left); frame 0001 first, then the sequence loads progressively (6 in flight) and the nearest loaded frame is shown until the exact one arrives. Image treatment (owner ruling): exposure +10 %, blacks lifted to ≥ #1E1A18 — applied as a canvas filter, so the film keeps its dusk and loses the murk.
function Film({ frame, box, W, H, label, still }) {
  const ref = useRef(null); const imgs = useRef([]); const want = useRef(frame); const shown = useRef(-1); const drawRef = useRef(null);
  const draw = useCallback(() => { const cv = ref.current; if (!cv) return; let i = want.current; if (!imgs.current[i]) { let best = -1, d = 1e9; imgs.current.forEach((im, k) => { if (im && Math.abs(k - i) < d) { d = Math.abs(k - i); best = k; } }); if (best < 0) return; i = best; } if (i === shown.current && cv.dataset.w === String(W) && cv.dataset.h === String(H)) return; const dpr = Math.min(window.devicePixelRatio || 1, 1.5); const cw = Math.round(W * dpr), ch = Math.round(H * dpr); if (cv.width !== cw || cv.height !== ch) { cv.width = cw; cv.height = ch; } cv.dataset.w = String(W); cv.dataset.h = String(H); const ctx = cv.getContext('2d'); const b = coverBox(W, H); ctx.filter = 'brightness(1.1) contrast(.94)'; ctx.drawImage(imgs.current[i], -b.x / b.k, -b.y / b.k, W / b.k, H / b.k, 0, 0, cw, ch); ctx.filter = 'none'; ctx.globalCompositeOperation = 'lighten'; ctx.fillStyle = '#1E1A18'; ctx.fillRect(0, 0, cw, ch); ctx.globalCompositeOperation = 'source-over'; shown.current = i; }, [W, H]);
  drawRef.current = draw;
  useEffect(() => { want.current = frame; draw(); }, [frame, draw]);
  useEffect(() => {
    let alive = true, next = 0, active = 0;
    const limit = still ? 1 : N;                                            // a still stage needs frame 0001 only
    const pump = () => { while (alive && active < 6 && next < limit) { const i = next++; active++; const im = new Image(); im.decoding = 'async'; im.onload = () => { if (!alive) return; active--; imgs.current[i] = im; drawRef.current(); pump(); }; im.onerror = () => { active--; pump(); }; im.src = frameSrc(i); } };
    pump(); return () => { alive = false; };
  }, []);
  return <canvas ref={ref} className="hero-film" role="img" aria-label={label} style={{ width: W, height: H }} />;
}

function Hero({ t, locale, reduced, mobile, tablet, onProgress, bound = true }) {
  const compact = mobile || tablet;
  const [userStill, setUserStill] = useState(() => { try { return localStorage.getItem('scenio.hero.still') === '1'; } catch (e) { return false; } });
  const still = mobile || reduced || userStill;                                             // < 640: Frame A only — no film, no callouts, no legend (owner ruling 1)
  const navH = mobile ? NAV.mobile : NAV.desktop;
  const trackRef = useRef(null), stageRef = useRef(null), copyRef = useRef(null), searchRef = useRef(null);
  const [p, setP] = useState(0);
  const [stage, setStage] = useState({ w: 1280, h: 800 });
  const [obstacles, setObstacles] = useState([]);
  const [mounted, setMounted] = useState(false);
  const [searchInFlow, setSearchInFlow] = useState(mobile);
  useEffect(() => { const id = setTimeout(() => setMounted(true), 350); return () => clearTimeout(id); }, []);

  // stage size
  useLayoutEffect(() => {
    const el = stageRef.current; if (!el) return;
    const m = () => { const r = el.getBoundingClientRect(); setStage((s) => (Math.abs(s.w - r.width) < .5 && Math.abs(s.h - r.height) < .5 ? s : { w: r.width, h: r.height })); };
    m(); const ro = 'ResizeObserver' in window ? new ResizeObserver(m) : null; if (ro) ro.observe(el); window.addEventListener('resize', m); return () => { if (ro) ro.disconnect(); window.removeEventListener('resize', m); };
  }, []);
  // static obstacles (copy block, search panel) for the box placement; on a short stage the search cannot sit under the copy — it moves into the flow after the track (the mobile rule)
  const searchReserve = (w) => (w < 1024 ? 200 : 86);
  useLayoutEffect(() => {
    const m = () => { const st = stageRef.current; if (!st) return; const sr = st.getBoundingClientRect(); const rect = (el) => { if (!el) return null; const r = el.getBoundingClientRect(); return { x: r.left - sr.left, y: r.top - sr.top, w: r.width, h: r.height }; }; const copy = rect(copyRef.current), search = rect(searchRef.current);
      const fits = !copy || !search || search.y >= copy.y + copy.h + 16; setSearchInFlow(mobile || (searchInFlow ? (copy ? copy.y + copy.h + 16 + searchReserve(sr.width) > sr.height : false) : !fits));
      setObstacles([copy, search].filter((r) => r && r.h > 0 && r.y < sr.height)); };
    m(); if (document.fonts) document.fonts.ready.then(m); const id = setTimeout(m, 400); return () => clearTimeout(id);
  }, [stage.w, stage.h, compact, mobile, t, searchInFlow]);

  // progress: pTarget = clamp(−trackTop / (heroHeight − viewport), 0, 1); pRendered += (pTarget − pRendered) × .14 per frame, initialised pRendered = pTarget at mount (before the first paint), snap at .0003. The header is transparent while the stage is stuck.
  useLayoutEffect(() => {
    let target = 0, cur = 0, raf = null, alive = true, stuck = true, pinned = null;
    const measure = () => { if (pinned != null) { target = pinned; stuck = true; return; } const el = trackRef.current; if (!el) return; const r = el.getBoundingClientRect(); const vh = window.innerHeight; const range = el.offsetHeight - vh; target = range > 0 ? clamp(-r.top / range, 0, 1) : (r.top < 0 ? 1 : 0); stuck = r.bottom - vh > 1; };
    const tick = () => { raf = null; if (!alive) return; cur += (target - cur) * (reduced ? 1 : FOLLOW); if (Math.abs(target - cur) < .0003) cur = target; setP(cur); onProgress && onProgress(cur, stuck); if (cur !== target) raf = requestAnimationFrame(tick); };
    const onScroll = () => { measure(); if (raf == null) raf = requestAnimationFrame(tick); };
    measure(); cur = target; setP(cur); onProgress && onProgress(cur, stuck);
    window.addEventListener('scroll', onScroll, { passive: true }); window.addEventListener('resize', onScroll);
    window.__scenioHeroScrollTo = (pp) => { const el = trackRef.current; if (!el) return; const vh = window.innerHeight; const top = el.getBoundingClientRect().top + window.scrollY; window.scrollTo({ top: top + pp * (el.offsetHeight - vh), behavior: 'instant' }); };
    // prototype hooks: pin p without scrolling (review frames), unpin to return to native scroll
    window.__scenioHeroSetP = (pp) => { pinned = clamp(pp, 0, 1); target = cur = pinned; setP(pinned); onProgress && onProgress(pinned, true); };
    window.__scenioHeroUnpin = () => { pinned = null; onScroll(); };
    return () => { alive = false; window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); if (raf) cancelAnimationFrame(raf); };
  }, [mobile, reduced]);

  const W = stage.w, H = stage.h;
  const box = useMemo(() => coverBox(W, H), [W, H]);
  const eff = still ? 0 : p;
  const fFloat = 1 + clamp(eff / FILM_END, 0, 1) * (N - 1);                 // 1-based film frame, fractional (for the keyed anchors)
  const frame = Math.round(fFloat) - 1;                                        // 0-based index drawn
  const toPx = (fx, fy) => ({ x: box.x + fx / 100 * box.w, y: box.y + fy / 100 * box.h });
  // anchors: the A set keyed per frame, the B set fixed on frame 0097
  const anchors = useMemo(() => { const o = {}; SET_A.forEach((c) => { const k = keyed(c.keys, fFloat); o[c.id] = toPx(k.x, k.y); }); SET_B.forEach((c) => { o[c.id] = toPx(c.anchor[0], c.anchor[1]); }); return o; }, [fFloat, box]);
  // item 5: the text of every callout — category · bound name · cta · href (bound = false renders the fallback to the category listing)
  const texts = useMemo(() => Object.fromEntries(ALL.map((c) => [c.id, CFG.texts(c.id, locale, bound, t.callouts)])), [t, locale, bound]);
  // A boxes: text-driven 112–200; B boxes: text-driven from 96 up to the wall right of the door at THIS stage — the name wraps inside it
  const wall = useMemo(() => wallWidth(W, box, CFG.bBox.right, CFG.bBox.minWidth), [W, box]);
  const sizes = useMemo(() => Object.fromEntries(ALL.map((c) => { const x = texts[c.id]; const b = SET_B.includes(c); return [c.id, boxSize(x.label, x.name, b ? CFG.bBox.minWidth : CFG.aBox.minWidth, b ? wall : CFG.aBox.maxWidth, x.cta)]; })), [texts, wall]);
  const boxes = useMemo(() => {
    const head = { x: 0, y: 0, w: W, h: navH };
    const door = { x: box.x + DOOR_B.left / 100 * box.w, y: box.y + DOOR_B.top / 100 * box.h, w: (DOOR_B.right - DOOR_B.left) / 100 * box.w, h: (DOOR_B.bottom - DOOR_B.top) / 100 * box.h, m: 0 };
    // A boxes travel with their anchors: the p = 0 offset (box − anchor) is kept
    const prefA = {}; SET_A.forEach((c) => { const a0 = toPx(c.keys[0][1], c.keys[0][2]); const b0 = toPx(c.box[0], c.box[1]); const a = anchors[c.id]; prefA[c.id] = { x: a.x + (b0.x - a0.x), y: a.y + (b0.y - a0.y) }; });
    // B boxes: one right-pinned column (right edge = W − 24px) stacked from the authored tops (40 / 47.5 / 55 %) with 8 px between — taller boxes push the next down, the column moves up off the hydrangeas
    const bPx = Object.fromEntries(SET_B.map((c) => [c.id, anchors[c.id]]));
    const prefB = columnB(SET_B, sizes, bPx, W, H, (top) => toPx(0, top).y, CFG.bBox.right, CFG.bBox.gap);
    const aPx = Object.fromEntries(SET_A.map((c) => [c.id, anchors[c.id]]));
    return { ...placeSet(SET_A, prefA, aPx, sizes, W, H, [...obstacles, head]), ...placeSet(SET_B, prefB, bPx, sizes, W, H, [...obstacles, head, door]) };
  }, [anchors, box, obstacles, sizes, W, H, navH]);
  const visible = (a) => a && a.x > 2 && a.x < W - 2 && a.y > navH && a.y < H - 2 && !obstacles.some((o) => a.x > o.x - 6 && a.x < o.x + o.w + 6 && a.y > o.y - 6 && a.y < o.y + o.h + 6);
  const aAlpha = (c) => 1 - seg(fFloat, c.out - c.fade, c.out);               // each A callout fades over its last `fade` frames
  const aShown = (c) => mounted && fFloat >= c.in && fFloat < c.out;
  const bShownWave = (w) => !compact && !still && eff >= WAVE_B[w];
  const legendSet = compact && !mobile ? (eff < WAVE_B[0] ? 'a' : 'b') : null;
  const trackH = still ? '100vh' : 'max(340vh, 2100px)';

  return (
    <>
    <section className="hero-track" ref={trackRef} style={{ height: trackH }} aria-label={t.hero.eyebrow}>
      <div className="hero-stage" data-on-photo="" ref={stageRef}>
        <Film frame={frame} box={box} W={W} H={H} still={still} label={frame < 48 ? t.hero.frameA : t.hero.frameB} />
        <div className="hero-scrim hero-scrim-copy" /><div className="hero-scrim hero-scrim-top" />
        {!reduced && <button type="button" className="hero-still" aria-pressed={userStill} aria-label={userStill ? (locale === 'pl' ? 'Włącz animację przewijania' : 'Turn the scroll animation on') : (locale === 'pl' ? 'Wyłącz animację przewijania — zostań przy pierwszym kadrze' : 'Turn the scroll animation off — stay on the first frame')} title={userStill ? (locale === 'pl' ? 'Włącz animację przewijania' : 'Turn the scroll animation on') : (locale === 'pl' ? 'Wyłącz animację przewijania — zostań przy pierwszym kadrze' : 'Turn the scroll animation off — stay on the first frame')} onClick={() => { const v = !userStill; setUserStill(v); try { localStorage.setItem('scenio.hero.still', v ? '1' : '0'); } catch (e) {} window.scrollTo({ top: 0 }); }}>{React.createElement(window.ScenioPaperCrimson_b9a328.UIcon, { name: userStill ? 'i-hugeicons-play' : 'i-hugeicons-pause', size: 18 })}</button>}
        {!compact && (!still || (userStill && !mobile && !reduced)) && <div className="hero-callouts">
          {SET_A.map((c, i) => { const a = anchors[c.id], b = boxes[c.id], x = texts[c.id]; if (!a || !b) return null; const vis = visible(a); return (
            <div key={c.id} className="hero-co" style={{ opacity: (userStill ? 1 : aAlpha(c)) * (vis ? 1 : 0) }}><ScenioCallout label={x.label} name={x.name} cta={x.cta} href={x.href} ax={a.x} ay={a.y} bx={b.x} by={b.y} minWidth={CFG.aBox.minWidth} maxWidth={CFG.aBox.maxWidth} shown={aShown(c) && vis} delay={LAUNCH[i] ?? i * STAGGER} reduced={reduced} /></div>
          ); })}
          {!userStill && SET_B.map((c) => { const a = anchors[c.id], b = boxes[c.id], x = texts[c.id]; if (!a || !b) return null; const vis = visible(a); return (
            <div key={c.id} className="hero-co" style={{ opacity: vis ? 1 : 0 }}><ScenioCallout label={x.label} name={x.name} cta={x.cta} href={x.href} ax={a.x} ay={a.y} bx={b.x} by={b.y} minWidth={CFG.bBox.minWidth} maxWidth={wall} shown={bShownWave(c.wave) && vis} delay={SET_B.filter((x) => x.wave === c.wave).indexOf(c) * STAGGER} reduced={reduced} /></div>
          ); })}
        </div>}
        <Copy ref={copyRef} t={t} reduced={reduced} still={userStill} legendSet={legendSet} texts={texts} />
        {!searchInFlow && <SearchPanel ref={searchRef} t={t} locale={locale} />}
      </div>
    </section>
    {searchInFlow && <div className="hero-search-flow"><SearchPanel t={t} locale={locale} size={mobile ? 'lg' : 'md'} /></div>}
    </>
  );
}
window.ScenioLanding = Object.assign(window.ScenioLanding || {}, { Hero, RotatingWord, Copy, SearchPanel, Film, Legend, coverBox, keyed, boxSize, columnB, wallWidth, placeSet, SET_A, SET_B, DOOR_B, N, FILM_END, WAVE_B, DWELL, FOLLOW, NAV });
})();
