/* Shared chrome for the Loncin website recreation. Exports to window. */

const ASSET = 'assets';

/* Resolve an asset path to its bundled blob URL when present (standalone export);
   otherwise return the original path unchanged, so the live site is unaffected. */
window.RES = window.RES || function (p) {
  try {
    const id = window.__R && window.__R[p];
    return (id && window.__resources && window.__resources[id]) ? window.__resources[id] : p;
  } catch (e) { return p; }
};

/* The site's real external links. */
const LINKS = {
  menu: 'https://loncinmenu.be/',
  suggesties: 'https://loncinmenu.be/?page_id=379',
  tour: 'https://my.matterport.com/show/?m=oU5YxV1Znb5',
  facebook: 'https://www.facebook.com/restaurantloncin',
  instagram: 'https://www.instagram.com/restaurantloncin/',
  tel: 'tel:+3232482989',
  mail: 'mailto:info@loncinrestaurant.be',
  maps: 'https://www.google.com/maps?q=Restaurant+Loncin,+Markgravelei+127,+2018+Antwerpen&hl=nl&z=16&output=embed',
};

/* Primary navigation — internal screens, in the house's own order.
   On wide screens the bar reads on a single line: PRIMARY · Meer▾ · END.
   The "Meer" dropdown gathers the occasional pages (35 Jaar, Events, Impressie).
   NAV (full, flat) is reused for the mobile sheet. */
const NAV_PRIMARY = [
  ['home', 'Home'],
  ['kaart', 'Menu'],
  ['kreeften', 'Seizoensmenu'],
  ['tallships', 'Tall Ships 2026'],
];
const NAV_MORE = [
  ['jubileum', '35 Jaar'],
  ['events', 'Events'],
  ['impressie', 'Impressie'],
  ['cadeaubon', 'Cadeaubon'],
];
const NAV_END = [
  ['contact', 'Contact'],
];
const NAV = [...NAV_PRIMARY, ...NAV_MORE, ...NAV_END];

/* Atmospheric placeholder where real photography will go. */
function PhotoFrame({ ratio = '4 / 3', label = 'Foto volgt', tall = false, src = null, position = 'center', style = {} }) {
  return (
    <div
      style={{
        position: 'relative',
        aspectRatio: ratio,
        width: '100%',
        background: 'radial-gradient(120% 120% at 50% 0%, #221F1A 0%, #141210 100%)',
        border: '1px solid var(--line-on-dark)',
        borderRadius: 'var(--radius-card)',
        overflow: 'hidden',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        ...style,
      }}
    >
      {src ? (
        <img
          src={window.RES(`${ASSET}/${src}`)}
          alt={label}
          loading="lazy"
          style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: position }}
        />
      ) : (
        <img
          src={window.RES(`${ASSET}/crest-gold.png`)}
          alt=""
          style={{ height: tall ? 120 : 84, opacity: 0.16, filter: 'saturate(0.7)' }}
        />
      )}
      {src && (
        <div
          aria-hidden="true"
          style={{
            position: 'absolute',
            inset: 0,
            background: 'linear-gradient(to top, rgba(15,13,11,0.86) 0%, rgba(15,13,11,0.46) 22%, rgba(15,13,11,0) 48%)',
          }}
        />
      )}
      <span
        style={{
          position: 'absolute',
          bottom: 14,
          left: 16,
          right: 16,
          fontFamily: 'var(--font-sans)',
          fontSize: 'var(--text-caption)',
          letterSpacing: 'var(--tracking-caps)',
          textTransform: 'uppercase',
          color: src ? 'var(--text-primary)' : 'var(--text-muted)',
          textShadow: src ? '0 1px 8px rgba(0,0,0,0.6)' : 'none',
        }}
      >
        {label}
      </span>
    </div>
  );
}

function Nav({ current, onNavigate }) {
  const { Button } = window.LoncinDesignSystem_6f2c51;
  const [open, setOpen] = React.useState(false);
  const [moreOpen, setMoreOpen] = React.useState(false);
  const [hidden, setHidden] = React.useState(false);
  const lastY = React.useRef(0);
  const idle = React.useRef(null);
  const openRef = React.useRef(false);
  openRef.current = open;

  // Auto-hide: fold up on scroll-down or after 10s idle; reveal on scroll-up,
  // mouse near the top edge, or hover over the bar itself.
  React.useEffect(() => {
    const armIdle = () => {
      clearTimeout(idle.current);
      idle.current = setTimeout(() => {
        if (window.scrollY > 90 && !openRef.current) setHidden(true);
      }, 10000);
    };
    const onScroll = () => {
      const y = window.scrollY;
      if (openRef.current) { setHidden(false); }
      else if (y < 90) { setHidden(false); }
      else if (y > lastY.current + 6) { setHidden(true); }
      else if (y < lastY.current - 6) { setHidden(false); }
      lastY.current = y;
      armIdle();
    };
    const onMove = (e) => {
      if (e.clientY < 90) { setHidden(false); armIdle(); }
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('mousemove', onMove, { passive: true });
    armIdle();
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('mousemove', onMove);
      clearTimeout(idle.current);
    };
  }, []);

  const linkStyle = (id) => ({
    fontFamily: 'var(--font-sans)',
    fontSize: 'var(--text-eyebrow)',
    letterSpacing: 'var(--tracking-caps)',
    textTransform: 'uppercase',
    whiteSpace: 'nowrap',
    color: current === id ? 'var(--metal-gold)' : 'var(--text-secondary)',
    transition: 'color var(--dur-fast) var(--ease-standard)',
    cursor: 'pointer',
  });

  const moreActive = NAV_MORE.some(([id]) => id === current);

  return (
    <header
      onMouseEnter={() => setHidden(false)}
      style={{
        position: 'sticky',
        top: 0,
        zIndex: 30,
        background: 'rgba(17,15,13,0.86)',
        backdropFilter: 'blur(10px)',
        borderBottom: '1px solid var(--line-on-dark)',
        transform: hidden ? 'translateY(-100%)' : 'translateY(0)',
        transition: 'transform var(--dur-slow, 0.4s) var(--ease-standard)',
        willChange: 'transform',
      }}
    >
      {/* Single line: brand · centred nav · reserve */}
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          gap: 'clamp(0.8rem, 2vw, 2rem)',
          padding: '0.95rem clamp(1.1rem, 4vw, 3.5rem)',
        }}
      >
        {/* Brand — left third */}
        <div style={{ flex: '1 1 0', display: 'flex', justifyContent: 'flex-start', minWidth: 0 }}>
          <a
            href="#"
            onClick={(e) => { e.preventDefault(); onNavigate('home'); }}
            style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}
          >
            <img src={window.RES(`${ASSET}/crest-gold.png`)} alt="Loncin" style={{ height: 40 }} />
            <span
              style={{
                fontFamily: 'var(--font-display)',
                fontSize: '1.35rem',
                letterSpacing: '0.18em',
                color: 'var(--text-primary)',
                textTransform: 'uppercase',
              }}
            >
              Loncin
            </span>
          </a>
        </div>

        {/* Centre nav — wide screens only */}
        <nav
          className="nav-links-row"
          style={{
            display: 'flex',
            alignItems: 'center',
            gap: 'clamp(0.8rem, 1.7vw, 1.7rem)',
            flex: '0 0 auto',
          }}
        >
          {NAV_PRIMARY.map(([id, label]) => (
            <a key={id} href="#" onClick={(e) => { e.preventDefault(); onNavigate(id); }} style={linkStyle(id)}>{label}</a>
          ))}

          {/* "Meer" dropdown */}
          <div
            style={{ position: 'relative', display: 'flex', alignItems: 'center' }}
            onMouseEnter={() => setMoreOpen(true)}
            onMouseLeave={() => setMoreOpen(false)}
          >
            <button
              type="button"
              className="nav-more-trigger"
              aria-haspopup="true"
              aria-expanded={moreOpen}
              onClick={() => setMoreOpen((o) => !o)}
              style={{
                display: 'inline-flex',
                alignItems: 'center',
                gap: '0.42em',
                background: 'transparent',
                border: 'none',
                padding: 0,
                fontFamily: 'var(--font-sans)',
                fontSize: 'var(--text-eyebrow)',
                letterSpacing: 'var(--tracking-caps)',
                textTransform: 'uppercase',
                whiteSpace: 'nowrap',
                cursor: 'pointer',
                color: (moreActive || moreOpen) ? 'var(--metal-gold)' : 'var(--text-secondary)',
                transition: 'color var(--dur-fast) var(--ease-standard)',
              }}
            >
              Meer
              <span
                aria-hidden="true"
                style={{
                  fontSize: '0.62em',
                  lineHeight: 1,
                  transform: moreOpen ? 'rotate(180deg)' : 'rotate(0deg)',
                  transition: 'transform var(--dur-fast) var(--ease-standard)',
                }}
              >
                ▾
              </span>
            </button>

            {moreOpen && (
              <div style={{ position: 'absolute', top: '100%', left: '50%', transform: 'translateX(-50%)', paddingTop: '0.85rem', zIndex: 40 }}>
                <div
                  style={{
                    minWidth: 188,
                    display: 'flex',
                    flexDirection: 'column',
                    gap: '0.1rem',
                    padding: '0.45rem',
                    background: 'rgba(20,18,15,0.97)',
                    backdropFilter: 'blur(14px)',
                    border: '1px solid var(--line-on-dark-strong)',
                    borderRadius: 'var(--radius-card)',
                    boxShadow: '0 18px 40px -16px rgba(0,0,0,0.7)',
                  }}
                >
                  {NAV_MORE.map(([id, label]) => (
                    <a
                      key={id}
                      href="#"
                      className="nav-drop-item"
                      onClick={(e) => { e.preventDefault(); setMoreOpen(false); onNavigate(id); }}
                      style={{
                        display: 'block',
                        padding: '0.62rem 0.85rem',
                        borderRadius: 'var(--radius-sm)',
                        fontFamily: 'var(--font-sans)',
                        fontSize: 'var(--text-eyebrow)',
                        letterSpacing: 'var(--tracking-caps)',
                        textTransform: 'uppercase',
                        whiteSpace: 'nowrap',
                        transition: 'color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard)',
                        color: current === id ? 'var(--metal-gold)' : 'var(--text-secondary)',
                      }}
                    >
                      {label}
                    </a>
                  ))}
                </div>
              </div>
            )}
          </div>

          {NAV_END.map(([id, label]) => (
            <a key={id} href="#" onClick={(e) => { e.preventDefault(); onNavigate(id); }} style={linkStyle(id)}>{label}</a>
          ))}
        </nav>

        {/* Actions — right third */}
        <div style={{ flex: '1 1 0', display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: '0.7rem' }}>
          <Button variant="primary" size="sm" onClick={() => onNavigate('reserve')}>Reserveren</Button>
          <button
            type="button"
            aria-label="Menu"
            onClick={() => setOpen((o) => !o)}
            className="nav-burger"
            style={{
              display: 'none',
              cursor: 'pointer',
              background: 'transparent',
              border: '1px solid var(--line-on-dark-strong)',
              borderRadius: 'var(--radius-sm)',
              color: 'var(--text-primary)',
              width: 40,
              height: 40,
              fontFamily: 'var(--font-sans)',
              fontSize: 'var(--text-eyebrow)',
              letterSpacing: 'var(--tracking-caps)',
            }}
          >
            {open ? '✕' : '☰'}
          </button>
        </div>
      </div>

      {/* Mobile dropdown */}
      {open && (
        <nav
          className="nav-mobile"
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: '0.1rem',
            padding: '0.5rem clamp(1.1rem, 4vw, 3.5rem) 1.2rem',
            borderTop: '1px solid var(--line-on-dark)',
          }}
        >
          {NAV.map(([id, label]) => (
            <a
              key={id}
              href="#"
              onClick={(e) => { e.preventDefault(); setOpen(false); onNavigate(id); }}
              style={{ ...linkStyle(id), padding: '0.7rem 0', borderBottom: '1px solid var(--line-on-dark)' }}
            >
              {label}
            </a>
          ))}
        </nav>
      )}
    </header>
  );
}

/* Standard dark page header with eyebrow + display title + optional intro. */
function PageHead({ eyebrow, title, intro, children }) {
  const { Eyebrow } = window.LoncinDesignSystem_6f2c51;
  return (
    <section
      style={{
        textAlign: 'center',
        padding: 'var(--space-8) var(--gutter) var(--space-6)',
        maxWidth: 'var(--container-prose)',
        margin: '0 auto',
      }}
    >
      <Eyebrow align="center" rule>{eyebrow}</Eyebrow>
      <h1
        style={{
          fontFamily: 'var(--font-display)',
          fontWeight: 400,
          fontSize: 'var(--text-display-m)',
          color: 'var(--text-primary)',
          margin: '1.3rem 0 0',
          letterSpacing: 'var(--tracking-display)',
          lineHeight: 'var(--leading-tight)',
        }}
      >
        {title}
      </h1>
      {intro && (
        <p
          style={{
            fontFamily: 'var(--font-serif)',
            fontSize: 'var(--text-body-lg)',
            color: 'var(--text-secondary)',
            lineHeight: 'var(--leading-relaxed)',
            marginTop: '1.3rem',
          }}
        >
          {intro}
        </p>
      )}
      {children}
    </section>
  );
}

function MapEmbed({ height = 360 }) {
  return (
    <iframe
      title="Restaurant Loncin — Markgravelei 127, 2018 Antwerpen"
      src={LINKS.maps}
      style={{
        display: 'block',
        width: '100%',
        height,
        border: '1px solid var(--line-on-dark)',
        borderRadius: 'var(--radius-card)',
        background: 'var(--surface-raised, #1a1714)',
        filter: 'grayscale(0.25) contrast(1.02)',
      }}
      loading="lazy"
      referrerPolicy="no-referrer-when-downgrade"
    />
  );
}

function Footer({ onNavigate }) {
  const { Ornament } = window.LoncinDesignSystem_6f2c51;
  const social = {
    fontFamily: 'var(--font-sans)',
    fontSize: 'var(--text-eyebrow)',
    letterSpacing: 'var(--tracking-caps)',
    textTransform: 'uppercase',
    color: 'var(--text-secondary)',
  };
  return (
    <footer
      style={{
        background: 'var(--surface-deep)',
        borderTop: '1px solid var(--line-on-dark)',
        padding: 'var(--space-9) clamp(1.25rem, 4vw, 3.5rem) var(--space-7)',
        textAlign: 'center',
      }}
    >
      <img src={window.RES(`${ASSET}/crest-gold.png`)} alt="Loncin" style={{ height: 72, margin: '0 auto', opacity: 0.95 }} />
      <div style={{ margin: '1.6rem auto', maxWidth: 320 }}><Ornament /></div>
      <div
        style={{
          fontFamily: 'var(--font-display)',
          fontSize: '1.6rem',
          letterSpacing: '0.16em',
          textTransform: 'uppercase',
          color: 'var(--text-primary)',
        }}
      >
        Restaurant Loncin
      </div>
      <div
        style={{
          marginTop: '1rem',
          display: 'flex',
          gap: '1.4rem',
          justifyContent: 'center',
          flexWrap: 'wrap',
          fontFamily: 'var(--font-serif)',
          color: 'var(--text-secondary)',
          fontSize: 'var(--text-small)',
        }}
      >
        <span>Markgravelei 127 — 2018 Antwerpen</span>
        <span style={{ color: 'var(--line-on-dark-strong)' }}>·</span>
        <a href={LINKS.tel} style={{ color: 'var(--text-secondary)' }}>+32 3 248 29 89</a>
        <span style={{ color: 'var(--line-on-dark-strong)' }}>·</span>
        <a href={LINKS.mail} style={{ color: 'var(--text-secondary)' }}>info@loncinrestaurant.be</a>
      </div>

      <div style={{ marginTop: '1.8rem', display: 'flex', gap: '1.6rem', justifyContent: 'center', flexWrap: 'wrap' }}>
        <a href={LINKS.facebook} target="_blank" rel="noopener noreferrer" style={social}>Facebook</a>
        <a href={LINKS.instagram} target="_blank" rel="noopener noreferrer" style={social}>Instagram</a>
        <a href="#" onClick={(e) => { e.preventDefault(); onNavigate && onNavigate('cadeaubon'); }} style={social}>Cadeaubon</a>
      </div>

      <div
        style={{
          marginTop: '2.5rem',
          fontFamily: 'var(--font-sans)',
          fontSize: 'var(--text-caption)',
          letterSpacing: 'var(--tracking-wide)',
          textTransform: 'uppercase',
          color: 'var(--text-muted)',
        }}
      >
        BTW BE0430 932 396 · Herenhuis anno 1902 · © 2026 Restaurant Loncin
      </div>

      <div
        style={{
          marginTop: '0.9rem',
          display: 'flex',
          gap: '1rem',
          justifyContent: 'center',
          flexWrap: 'wrap',
          fontFamily: 'var(--font-sans)',
          fontSize: 'var(--text-caption)',
          letterSpacing: 'var(--tracking-wide)',
          textTransform: 'uppercase',
          color: 'var(--text-muted)',
        }}
      >
        <a href="#" onClick={(e) => { e.preventDefault(); onNavigate && onNavigate('cookies'); }} style={{ color: 'var(--text-muted)', textDecoration: 'none' }}>Cookiebeleid</a>
        <span style={{ color: 'var(--line-on-dark-strong)' }}>·</span>
        <a href="#" onClick={(e) => { e.preventDefault(); onNavigate && onNavigate('privacy'); }} style={{ color: 'var(--text-muted)', textDecoration: 'none' }}>Privacyverklaring</a>
        <span style={{ color: 'var(--line-on-dark-strong)' }}>·</span>
        <a href="#" onClick={(e) => { e.preventDefault(); onNavigate && onNavigate('disclaimer'); }} style={{ color: 'var(--text-muted)', textDecoration: 'none' }}>Disclaimer</a>
      </div>
    </footer>
  );
}

Object.assign(window, { PhotoFrame, Nav, Footer, PageHead, MapEmbed, LINKS });

/* ===== Global scroll-reveal =====================================================
   Every screen reveals element-by-element, section by section, as it scrolls into
   view: a gentle fade + lift with a per-section stagger so children cascade in.
   Targets the meaningful content children of each <section> (and the <footer>),
   plus any element opted in with [data-reveal]; skips sticky/fixed chrome and
   absolutely-positioned decoration. Re-scans on screen swaps via a MutationObserver,
   so no per-screen wiring is needed. Driven entirely by inline styles (highest
   specificity, untouched by React's style reconciliation, so it survives re-renders);
   reveal is scroll/position based for maximum reliability. Honours prefers-reduced-motion.
   Debug: window.__loncinRevealDebug() logs armed/shown/hidden counts for the live screen. */
(function setupLoncinReveal() {
  if (window.__loncinRevealReady) return;
  window.__loncinRevealReady = true;

  const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const EASE = 'cubic-bezier(0.22, 0.61, 0.27, 1)';
  const DUR = 950;        // ms — a touch slower
  const STAGGER = 110;    // ms between siblings within a section
  const MAX_STAGGER = 660;// ms cap so long sections don't drag
  const LIFT = '24px';
  const THRESH = 0.84;    // reveal a little later: top must clear into the bottom ~16% of the viewport
  const INTRO_BASE = 240; // ms — initial intro waits a beat before it begins
  const SCROLL_BASE = 60; // ms — small lead on subsequent scroll reveals

  let firstBatch = true;
  const pending = [];
  let editing = false; // when the host editor is active, never keep anything hidden

  // The host editor (Edit / Comment / Design modes) stamps nodes with these attrs.
  // While editing, the page can be frozen mid-animation — which would leave armed
  // elements stuck at opacity:0 (a black screen). Detect it and reveal everything.
  function editActive() {
    try {
      return !!document.querySelector('[data-cc-id],[data-dm-ref]') ||
        document.documentElement.hasAttribute('data-dm-active') ||
        document.body.hasAttribute('data-editing');
    } catch (err) { return false; }
  }

  function isAnimatable(el) {
    if (!el || el.nodeType !== 1) return false;
    const t = el.tagName;
    if (t === 'SCRIPT' || t === 'STYLE' || t === 'TEMPLATE' || t === 'BR') return false;
    let pos = '';
    try { pos = getComputedStyle(el).position; } catch (err) { /* noop */ }
    if (pos === 'absolute' || pos === 'fixed' || pos === 'sticky') return false; // leave overlays/backdrops/chrome alone
    return true;
  }

  function targetsForContainer(root, owner) {
    let container = root;
    // Unwrap single <div> wrappers so we stagger the real content, not an outer shell.
    for (let i = 0; i < 6; i++) {
      const kids = [...container.children].filter(isAnimatable);
      if (kids.length === 1 && kids[0].tagName === 'DIV') container = kids[0];
      else break;
    }
    let targets = [...container.children].filter(isAnimatable);
    if (targets.length === 0) targets = [root];
    targets.forEach((el) => { el.__rvSection = owner; });
    return targets;
  }

  function collectTargets() {
    const main = document.querySelector('main');
    const out = [];
    if (main) {
      main.querySelectorAll('section').forEach((sec) => {
        let pos = '';
        try { pos = getComputedStyle(sec).position; } catch (err) { /* noop */ }
        if (pos === 'sticky' || pos === 'fixed') return;
        targetsForContainer(sec, sec).forEach((el) => out.push(el));
      });
      main.querySelectorAll('[data-reveal]').forEach((el) => { el.__rvSection = el; out.push(el); });
    }
    document.querySelectorAll('footer').forEach((ft) => {
      targetsForContainer(ft, ft).forEach((el) => out.push(el));
    });
    return out;
  }

  function arm(el) {
    if (el.__rvArmed) return;
    el.__rvArmed = true;
    if (reduce || editing) { el.__rvShown = true; return; }
    // Marker so the CSS failsafe (in index.html head) can force this element
    // visible the instant the editor activates — even if JS/timers are frozen.
    el.setAttribute('data-rv', '');
    el.style.transition = `opacity ${DUR}ms ${EASE}, transform ${DUR}ms ${EASE}`;
    el.style.willChange = 'opacity, transform';
    el.style.opacity = '0';
    el.style.transform = `translateY(${LIFT})`;
  }

  function show(el, delayMs) {
    if (el.__rvShown) return;
    el.__rvShown = true;
    if (reduce) return;
    el.style.transitionDelay = (delayMs || 0) + 'ms';
    el.style.opacity = '1';
    el.style.transform = 'none';
  }

  // Instantly un-hide every element (no transition) and stop hiding new ones.
  // Used when the editor activates or as a failsafe so content is never stuck dark.
  function revealAll() {
    editing = true;
    collectTargets().forEach((el) => {
      el.__rvArmed = true; el.__rvShown = true;
      el.style.transition = 'none';
      el.style.opacity = '1';
      el.style.transform = 'none';
      el.style.willChange = 'auto';
    });
    document.querySelectorAll('.ev-reveal:not(.is-in)').forEach((el) => el.classList.add('is-in'));
    pending.length = 0;
  }

  function scan() {
    collectTargets().forEach((el) => {
      if (el.__rvArmed) return;
      arm(el);
      if (!el.__rvShown) pending.push(el);
    });
  }

  function check() {
    if (reduce) { pending.length = 0; return; }
    const vh = window.innerHeight || 800;
    // When the page can't scroll any further, the last items may sit in the bottom
    // ~16% and never cross the normal threshold — so reveal anything visible at the end.
    const docH = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
    const atBottom = (window.innerHeight + window.scrollY) >= (docH - 2);
    const perSec = new Map();
    const base = firstBatch ? INTRO_BASE : SCROLL_BASE;
    const still = [];
    let revealedAny = false;
    for (const el of pending) { // pending is in document order → top-to-bottom cascade
      if (el.__rvShown) continue;
      const top = el.getBoundingClientRect().top;
      if (top <= -vh) {
        show(el, 0); // scrolled well past (e.g. a jump) — reveal instantly, no stall
      } else if (top < vh * THRESH || (atBottom && top < vh)) {
        const sec = el.__rvSection || el;
        const n = perSec.get(sec) || 0;
        perSec.set(sec, n + 1);
        show(el, base + Math.min(n * STAGGER, MAX_STAGGER));
        revealedAny = true;
      } else {
        still.push(el);
      }
    }
    pending.length = 0;
    Array.prototype.push.apply(pending, still);
    if (revealedAny) firstBatch = false;

    // Safety net for EventsScreen's IntersectionObserver-driven portfolio (.ev-reveal):
    // reveal any that have entered view, so the gallery never stays hidden if IO is flaky.
    const evs = document.querySelectorAll('.ev-reveal:not(.is-in)');
    if (evs.length) evs.forEach((el) => { if (el.getBoundingClientRect().top < vh) el.classList.add('is-in'); });
  }

  function tick() {
    scan();
    // Let the hidden state paint first, then reveal in-view blocks so the intro animates.
    requestAnimationFrame(() => requestAnimationFrame(check));
  }

  function start() {
    if (editActive()) editing = true; // editor already open — render everything visible
    tick();
    const root = document.getElementById('root');
    if (root && 'MutationObserver' in window) {
      new MutationObserver(() => { if (editActive()) revealAll(); else tick(); }).observe(root, { childList: true, subtree: true });
    }
    // Watch for the editor stamping its attributes anywhere, and un-hide instantly.
    if ('MutationObserver' in window) {
      new MutationObserver(() => { if (editActive()) revealAll(); }).observe(document.documentElement, {
        attributes: true, subtree: true, attributeFilter: ['data-cc-id', 'data-dm-ref', 'data-dm-active', 'data-editing'],
      });
    }
    window.addEventListener('scroll', check, { passive: true });
    window.addEventListener('resize', check);
    window.addEventListener('load', tick);
    setTimeout(check, 300);
    setTimeout(check, 800);
    // Ultimate failsafe: if anything in the first viewport is still hidden after a
    // moment (e.g. the page was frozen by tooling), reveal everything so it's never black.
    setTimeout(() => {
      if (editing) return;
      const vh = window.innerHeight || 800;
      const stuck = collectTargets().some((el) => el.__rvArmed && !el.__rvShown && el.getBoundingClientRect().top < vh * 0.5);
      if (stuck) revealAll();
    }, 2000);
  }

  // Debug helper — call window.__loncinRevealDebug() in the console.
  window.__loncinRevealDebug = function () {
    const all = collectTargets();
    const armed = all.filter((e) => e.__rvArmed).length;
    const shown = all.filter((e) => e.__rvShown).length;
    const hiddenStuck = all.filter((e) => e.__rvArmed && !e.__rvShown && e.getBoundingClientRect().top < (window.innerHeight || 800));
    const info = { targets: all.length, armed, shown, pending: pending.length, inViewButHidden: hiddenStuck.length };
    /* eslint-disable no-console */
    if (window.console) console.log('[Loncin reveal]', info, hiddenStuck.slice(0, 8));
    return info;
  };

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
  else start();
})();
