/* IJWestern Landing — main React app
   Sections: TopBar · Hero · Portfolio · Method · NewLaunch · Register · Footer
*/

const { useState, useEffect, useRef, useMemo } = React;

/* ── Project data ─────────────────────────────────────────────── */
/* The five real projects, from `Plot Details.docx` plus the status the
   client confirmed on 2 Sep 2026. This array drives the cards, the filter
   counts and the "Which ground" dropdown, so it is the single place a
   project's headline facts are stated on the home page.

   It previously held six entries of which five were fiction — Mattumandha,
   Golden Avenue and Nila Nagar do not exist, and the Blue Hills and Rose
   Garden rows carried invented plot counts, years and handover dates.
   Blue Hills Phase 1 is a real, older, completed project but the doc has
   nothing on it, so it stays off the site until the client supplies
   details. */
const PROJECTS = [
  {
    id: "blue-hills-phase-2",
    num: "01",
    name: "Blue Hills Phase 2",
    loc: "Thekkumury, Chandranagar",
    statusKind: "selling",
    statusLabel: "Layout complete · Sales on-going",
    plots: 69,
    cents: "4.40–15",
    href: "blue-hills-phase-2.html",
    photo: "img/IMG_2215.jpg",
    cardPhoto: "site-images/new-images/bluehills.webp",
    cardVideo: { mp4: "site-images/videos/cards/bluehills.mp4", webm: "site-images/videos/cards/bluehills.webm" },
    theme: "hills",
    /* Read by the New launch section. Move `launch` to the next
       project when one launches; the section follows the flag. */
    launch: true,
    /* Intro, district, photographs and the documents line all come
       from PROJECT_DATA — the card renders them from there so the
       copy is stated once for the whole site. */
  },
  {
    id: "ratham-gardens",
    num: "02",
    name: "Ratham Gardens",
    loc: "Vaidhyanathapuram, Kalpathy",
    statusKind: "selling",
    statusLabel: "Layout complete · Sales on-going",
    plots: 36,
    cents: "4.40–9",
    href: "ratham-gardens.html",
    cardPhoto: "site-images/new-images/rathamgardens.webp",
    cardVideo: { mp4: "site-images/videos/cards/rathamgardens.mp4?v=2", webm: "site-images/videos/cards/rathamgardens.webm?v=2" },
    theme: "hills",
  },
  {
    id: "star-gardens",
    num: "03",
    name: "Star Gardens",
    loc: "Vandithavalam",
    statusKind: "selling",
    statusLabel: "Layout complete · Sales on-going",
    plots: 41,
    cents: "4.20–9.30",
    href: "star-gardens.html",
    cardPhoto: "site-images/new-images/stargardens.webp",
    cardVideo: { mp4: "site-images/videos/cards/stargardens.mp4", webm: "site-images/videos/cards/stargardens.webm" },
    theme: "paddy",
  },
  {
    id: "dream-gardens",
    num: "04",
    name: "Dream Gardens",
    loc: "Thrippalur",
    statusKind: "selling",
    statusLabel: "Layout complete · Sales on-going",
    plots: 21,
    cents: "5.30–10",
    href: "dream-gardens.html",
    cardPhoto: "site-images/new-images/dreamgardens.webp",
    cardVideo: { mp4: "site-images/videos/cards/dreamgardens.mp4", webm: "site-images/videos/cards/dreamgardens.webm" },
    theme: "mist",
  },
  {
    id: "rose-gardens",
    num: "05",
    name: "Rose Gardens",
    loc: "Puthussery",
    statusKind: "progress",
    statusLabel: "Layout in-progress",
    plots: 15,
    cents: "5–7.5",
    href: "rose-gardens.html",
    cardPhoto: "site-images/new-images/rosegardens.webp",
    cardVideo: { mp4: "site-images/videos/cards/rosegardens.mp4", webm: "site-images/videos/cards/rosegardens.webm" },
    theme: "dawn",
  },
];

/* ── Hero ─────────────────────────────────────────────────────────
   Step 10 (4 Sep 2026). One photograph of the Palakkad paddy, held at
   viewport height in a rounded frame that clears the floating header
   and both edges. The geometry is all in styles.css under "Hero"; what
   lives here is the scroll motion and the copy.

   Motion. On load the frame opens and the photograph settles out of a
   slight over-scale (CSS). On scroll the photograph drifts down by
   HERO_PARALLAX of the frame's height while the copy lifts and fades
   the other way — the difference in the two speeds is the parallax.
   Both transforms are written straight to the node inside a
   requestAnimationFrame rather than through state: a re-render per
   scroll event would re-render the whole home page, carousel included.
   That is the same reason the About page's map tilt is written this
   way. Under prefers-reduced-motion the handler never attaches, so
   nothing moves at all.

   HERO_PARALLAX must stay at or below --hero-drift in styles.css, or
   the photograph runs out of slack and shows the frame beneath it. */
const HERO_PARALLAX = 0.08;   // photo drift, as a fraction of frame height
const HERO_COPY_LIFT = 44;    // px the copy rises across the same travel

function Hero() {
  const sectionRef = useRef(null);
  const photoRef = useRef(null);
  const copyRef = useRef(null);

  useEffect(() => {
    const section = sectionRef.current;
    const photo = photoRef.current;
    const copy = copyRef.current;
    if (!section || !photo) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;

    let raf = 0;
    const apply = () => {
      raf = 0;
      const h = section.offsetHeight || 1;
      const p = Math.min(1, Math.max(0, window.scrollY / h));
      photo.style.transform =
        "translate3d(0, " + (p * h * HERO_PARALLAX).toFixed(2) + "px, 0)";
      if (copy) {
        copy.style.transform =
          "translate3d(0, " + (-p * HERO_COPY_LIFT).toFixed(2) + "px, 0)";
        copy.style.opacity = String(Math.max(0, 1 - p * 1.4));
      }
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(apply); };

    apply();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  return (
    <section className="hero" ref={sectionRef}>
      <div className="hero-frame">
        <div className="hero-photo" ref={photoRef}>
          {/* Three sizes off one 5464px original — the full file is
              2.4MB and this is the first thing the page paints.
              tools/build-hero-image.py rebuilds them. */}
          <img
            src="site-images/pkd-impress-1600.jpg"
            srcSet={"site-images/pkd-impress-1000.jpg 1000w, " +
                    "site-images/pkd-impress-1600.jpg 1600w, " +
                    "site-images/pkd-impress-2560.jpg 2560w"}
            sizes="100vw"
            alt="Paddy fields, coconut palms and a village road under a bright morning sky in Palakkad"
            decoding="async"
          />
        </div>
        {/* Lifts the light at the top of the frame so ink type always
            has ground to stand on. See .hero-sky in styles.css. */}
        <div className="hero-sky" aria-hidden="true" />
        <div className="hero-copy" ref={copyRef}>
          <h1 className="t-display hero-headline">
            <span className="ln-1">Land you can see.</span>
            <span className="ln-2"><em>Ground</em> you can stand on.</span>
          </h1>
          <p className="t-lead hero-lead">
            Between the Ghats and the paddy, Palakkad keeps its own pace. We
            develop plotted communities inside it, documented in full, so what
            you own is a piece of the place itself.
          </p>
        </div>
        {/* Sibling of .hero-copy, not a child: the CTA sits at the foot
            of the photograph rather than in the copy's grid, and stays
            put while the copy lifts and fades on scroll. It enters as a
            gold dot and opens into the pill — heroCtaDot / heroCtaOpen
            in styles.css. */}
        <div className="hero-cta">
          <a href="projects.html" className="btn btn-primary">
            View all projects
            <span className="arrow"><ArrowIcon /></span>
          </a>
        </div>
      </div>
    </section>
  );
}

/* ── New launch ────────────────────────────────────────────────────
   A carousel of all five communities in one design (step 4, 3 Sep
   2026). The card the client approved is unchanged; there are simply
   five of them now, side by side in a scroll-snap track, and the
   entry carrying `launch: true` leads. Move that flag when the next
   project launches and the order follows — nothing else to rewrite.

   Card content comes from PROJECT_DATA, which is the same source the
   project pages read, so an intro or a document list is stated in one
   place only. The headline figures still come from the PROJECTS row
   beside them; the two agree, and CLAUDE.md says to keep them so.

   Motion: the track advances on its own, pauses on hover and on focus,
   stays put for nine seconds after the user scrolls it themselves, and
   does not auto-advance at all under prefers-reduced-motion. */

const NL_COUNT_WORDS = ["no", "one", "two", "three", "four", "five", "six",
                        "seven", "eight", "nine", "ten", "eleven", "twelve"];

/* "Form 5 & 6 approved · seven documents open for inspection", built
   from the project's own document list rather than written out per
   project — five hand-written copies of one sentence would drift. */
function nlDocsLine(id) {
  const data = (typeof PROJECT_DATA !== "undefined" && PROJECT_DATA[id]) || null;
  const docs = (data && data.documents) || [];
  if (!docs.length) return "";
  const approved = docs.filter(d => d.st === "Approved").map(d => d.nm);
  const word = NL_COUNT_WORDS[docs.length] || String(docs.length);
  const tail = word + " document" + (docs.length === 1 ? "" : "s") + " open for inspection";
  if (!approved.length) return tail.charAt(0).toUpperCase() + tail.slice(1);
  return approved.join(" · ") + " approved · " + tail;
}

function LaunchCard({ p, lead, active, held, reduced, index, total, onSchedule }) {
  const data = (typeof PROJECT_DATA !== "undefined" && PROJECT_DATA[p.id]) || {};
  /* One photograph per card, the same new image the portfolio card uses.
     The project pages keep their own galleries. */
  const shots = p.cardPhoto
    ? [{ src: p.cardPhoto, cap: projectNameInline(p.name) }]
    : (p.photo ? [{ src: p.photo, cap: p.name }] : []);
  const [shot, setShot] = useState(0);

  /* Cross-fade the photographs of the card being looked at. Only Blue
     Hills has more than one today; the rest fall through. */
  useEffect(() => {
    if (reduced || held || !active || shots.length < 2) return;
    const t = setInterval(() => setShot(s => (s + 1) % shots.length), 5200);
    return () => clearInterval(t);
  }, [reduced, held, active, shots.length]);

  const figures = [
    { v: String(p.plots), k: "Plots" },
    { v: p.cents, u: "cents", k: "Plot sizes" },
    { v: "Purayidam", k: "Classification" },
  ];
  /* Price left the cards on 2 Nov and Total area on 3 Nov (now
     Classification); road frontage takes the fourth tile, read from the
     project page's own spec rows. */
  const frontage = pcFrontage(p.id);
  if (frontage) figures.push({ v: frontage, u: "m", k: "Frontage" });
  const intro = data.intro || "";
  const docs = nlDocsLine(p.id);
  const district = data.district || p.district || "";
  const place = p.loc + (district ? " · " + district : "");

  return (
    <article className="nl-slide"
             role="group"
             aria-roledescription="slide"
             aria-label={(index + 1) + " of " + total + " — " + projectNameInline(p.name)}>
      <div className="nl-stage">
        <div className="nl-frames">
          {shots.length > 0
            ? shots.map((s, i) => (
                <img key={s.src} src={s.src} alt={s.cap}
                     loading={lead ? "eager" : "lazy"}
                     className={i === shot ? "is-live" : ""} />
              ))
            : <LandPlaceholder theme={p.theme || "hills"} seed={index + 1} label="" />}
        </div>
        <div className="nl-scrim" />

        {shots.length > 1 && (
          <div className="nl-dots">
            {shots.map((s, i) => (
              <button key={s.src} type="button"
                      className={"nl-dot" + (i === shot ? " is-on" : "")}
                      onClick={() => setShot(i)}
                      aria-label={"Show " + s.cap} />
            ))}
          </div>
        )}

        <div className="nl-over">
          <div className="nl-tags">
            {lead
              ? <span className="nl-tag is-new">Latest launch</span>
              : <span className="nl-tag is-num">No. {p.num}</span>}
            <span className={"nl-tag is-status" + (p.statusKind === "progress" ? " is-progress" : "")}>
              <span className="dot"></span>{p.statusLabel}
            </span>
          </div>
          <h2 className="t-display nl-name"><ProjectName name={p.name} /></h2>
          <p className="nl-place">{place}</p>
        </div>
      </div>

      <div className="nl-tiles">
        {figures.map(f => (
          <div className="nl-tile" key={f.k}>
            <div className="v">{f.v}{f.u && <span className="unit">{f.u}</span>}</div>
            <div className="k">{f.k}</div>
          </div>
        ))}
      </div>

      <div className="nl-foot">
        <div>
          {intro && <p className="nl-intro">{intro}</p>}
          {docs && (
            <p className="nl-docs">
              <span className="tick">
                <svg viewBox="0 0 12 12" width="9" height="9" fill="none" stroke="currentColor"
                     strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M2 6.4l2.6 2.6L10 3.2" />
                </svg>
              </span>
              {docs}
            </p>
          )}
        </div>
        <div className="nl-cta">
          {/* Opens the visit form on this card's project (client, 3 Nov). */}
          <button type="button" className="btn btn-secondary" onClick={() => onSchedule(p.name)}>
            Schedule a visit<span className="arrow"><ArrowIcon /></span>
          </button>
          <a href={p.href} className="btn btn-primary">
            See the project<span className="arrow"><ArrowIcon /></span>
          </a>
        </div>
      </div>
    </article>
  );
}

function NewLaunch({ launchRef, onSchedule }) {
  /* The launch project leads; the rest keep their portfolio order. */
  const order = useMemo(() => {
    const i = PROJECTS.findIndex(x => x.launch);
    return i > 0 ? [PROJECTS[i]].concat(PROJECTS.filter((_, j) => j !== i)) : PROJECTS.slice();
  }, []);
  const total = order.length;

  const [active, setActive] = useState(0);
  const [held, setHeld] = useState(false);
  /* The sliver of the next card is what says the row scrolls, but a
     hard cut through a sentence reads as a bug, so the track is masked
     wherever a card is only half shown. Snapped, that is the right edge
     — except on the last card, where the overhang is on the left. */
  const [atEnd, setAtEnd] = useState(false);
  const [ring, setRing] = useState(false);
  const trackRef = useRef(null);
  const secRef = useRef(null);
  const activeRef = useRef(0);
  /* Auto-advance stands down for this long after a manual scroll. */
  const quietUntil = useRef(0);
  /* Our own smooth scrolls must not read as a manual one. */
  const selfUntil = useRef(0);

  const reduced = typeof window !== "undefined" && window.matchMedia
    ? window.matchMedia("(prefers-reduced-motion: reduce)").matches
    : false;

  const setSection = (el) => {
    secRef.current = el;
    if (launchRef) launchRef.current = el;
  };

  const goTo = (i, smooth) => {
    const track = trackRef.current;
    if (!track || !total) return;
    const n = ((i % total) + total) % total;
    const slide = track.children[n];
    if (!slide) return;
    const left = track.scrollLeft
      + slide.getBoundingClientRect().left
      - track.getBoundingClientRect().left;
    selfUntil.current = Date.now() + 1200;
    activeRef.current = n;
    setActive(n);
    if (typeof track.scrollTo === "function") {
      track.scrollTo({ left, behavior: (reduced || smooth === false) ? "auto" : "smooth" });
    } else {
      track.scrollLeft = left;
    }
  };

  /* Which card is under the left edge — the source of truth for the
     dots, so dragging the track and pressing a dot agree. */
  useEffect(() => {
    const track = trackRef.current;
    if (!track) return;
    let raf = null;
    const onScroll = () => {
      if (Date.now() > selfUntil.current) quietUntil.current = Date.now() + 9000;
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = null;
        const base = track.getBoundingClientRect().left;
        let best = 0;
        let bestD = Infinity;
        Array.prototype.forEach.call(track.children, (el, i) => {
          const d = Math.abs(el.getBoundingClientRect().left - base);
          if (d < bestD) { bestD = d; best = i; }
        });
        activeRef.current = best;
        setActive(a => (a === best ? a : best));
        setAtEnd(track.scrollLeft >= track.scrollWidth - track.clientWidth - 2);
      });
    };
    onScroll();
    track.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll, { passive: true });
    return () => {
      track.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  useEffect(() => {
    if (reduced || held || total < 2) return;
    const t = setInterval(() => {
      if (document.hidden) return;
      /* No auto-advance on phones: a card is taller than the screen there
         and would slide away mid-read. Arrows, dots and swipe still work. */
      if (window.matchMedia("(max-width: 560px)").matches) return;
      if (Date.now() < quietUntil.current) return;
      goTo(activeRef.current + 1);
    }, 6800);
    return () => clearInterval(t);
  }, [reduced, held, total]);

  /* Parallax: the frames drift slower than the page. One listener for
     the section, since every card sits at the same height. */
  useEffect(() => {
    if (reduced) return;
    let raf = null;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = null;
        const el = secRef.current;
        if (!el) return;
        const r = el.getBoundingClientRect();
        const mid = r.top + r.height / 2 - window.innerHeight / 2;
        const shift = Math.max(-34, Math.min(34, (-mid / window.innerHeight) * 42));
        el.style.setProperty("--par", shift.toFixed(1) + "px");
      });
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => {
      window.removeEventListener("scroll", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [reduced]);

  const onKeyDown = (e) => {
    if (e.key === "ArrowRight") { e.preventDefault(); goTo(activeRef.current + 1); }
    else if (e.key === "ArrowLeft") { e.preventDefault(); goTo(activeRef.current - 1); }
  };

  return (
    <section className="new-launch" ref={setSection} id="new-launch"
             data-ground="dark" data-screen-label="New launch"
             onMouseEnter={() => setHeld(true)}
             onMouseLeave={() => setHeld(false)}
             onFocus={() => setHeld(true)}
             onBlur={() => setHeld(false)}>
      <div className="shell">
        <div className="nl-carousel reveal-fade">
          <div className={"nl-view" + (atEnd ? " at-end" : "") + (ring ? " is-focused" : "")}>
          <div className="nl-track" ref={trackRef} tabIndex={0}
               role="group" aria-label="The five communities"
               onKeyDown={onKeyDown}
               onFocus={(e) => { if (e.target === e.currentTarget && e.target.matches(":focus-visible")) setRing(true); }}
               onBlur={(e) => { if (e.target === e.currentTarget) setRing(false); }}
               onPointerDown={() => { quietUntil.current = Date.now() + 9000; }}>
            {order.map((p, i) => (
              <LaunchCard key={p.id} p={p} lead={i === 0} active={i === active}
                          held={held} reduced={reduced} index={i} total={total}
                          onSchedule={onSchedule} />
            ))}
          </div>
          </div>

          <div className="nl-rail">
            <div className="nl-rail-dots" role="group" aria-label="Choose a community">
              {order.map((p, i) => (
                <button key={p.id} type="button"
                        className={"nl-rail-dot" + (i === active ? " is-on" : "")}
                        aria-label={projectNameInline(p.name)}
                        aria-current={i === active ? "true" : undefined}
                        onClick={() => goTo(i)} />
              ))}
            </div>
            {/* Chevrons, not the site's diagonal arrow: rotated for "back",
              that arrow read as up and down (client, 3 Nov). */}
          <div className="nl-arrows">
              <button type="button" className="nl-arrow is-prev"
                      aria-label="Previous community"
                      onClick={() => goTo(activeRef.current - 1)}>
                <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor"
                     strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                  <path d="M5.5 2.5 L10 7 L5.5 11.5" />
                </svg>
              </button>
              <button type="button" className="nl-arrow"
                      aria-label="Next community"
                      onClick={() => goTo(activeRef.current + 1)}>
                <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor"
                     strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                  <path d="M5.5 2.5 L10 7 L5.5 11.5" />
                </svg>
              </button>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ── Portfolio ────────────────────────────────────────────────── */
/* Per-plot road frontage, "10–20", out of the project page's own spec rows. */
const pcFrontage = (id) => {
  const d = typeof PROJECT_DATA !== "undefined" && PROJECT_DATA[id];
  const row = d && d.specs && d.specs.flatMap(g => g.rows).find(r => r.k === "Road frontage");
  return row ? row.v.replace(/\s*–\s*/g, "–") : null;
};
function Portfolio({ projectsRef }) {
  const [filter, setFilter] = useState("All");
  const counts = useMemo(() => ({
    All: PROJECTS.length,
    "Sales on-going": PROJECTS.filter(p => p.statusKind === "selling").length,
    "Layout in-progress": PROJECTS.filter(p => p.statusKind === "progress").length,
  }), []);
  const KIND_FOR = { "Sales on-going": "selling", "Layout in-progress": "progress" };
  const visible = filter === "All" ? PROJECTS : PROJECTS.filter(p => p.statusKind === KIND_FOR[filter]);

  return (
    <section className="portfolio rule-top" ref={projectsRef} id="projects" data-screen-label="Projects">
      <div className="shell">
        <div className="portfolio-head">
          <div className="reveal">
            <h2><em>Five grounds.</em><br />One Palakkad.</h2>
          </div>
          <div className="right reveal" style={{ "--reveal-delay": "200ms" }}>
            <p className="t-lead" style={{ maxWidth: "38ch" }}>
              Every community is on land we identified, surveyed, and
              developed ourselves.
            </p>
          </div>
        </div>

        <div className="filter-row reveal" style={{ "--reveal-delay": "300ms" }}>
          {["All", "Sales on-going", "Layout in-progress"].map(k => (
            <button key={k}
              className={`chip ${filter === k ? "active" : ""}`}
              onClick={() => setFilter(k)}>
              {k}<span className="count">{String(counts[k]).padStart(2, "0")}</span>
            </button>
          ))}
        </div>

        <div className="plot-cards">
          {visible.map((p, i) => (
            <a
              key={p.id}
              href={p.href}
              className="pc reveal"
              style={{ "--reveal-delay": `${120 + i * 90}ms` }}
              aria-label={`${p.name}, ${p.loc}`}>

              <div className="pc-bg">
                {(p.cardPhoto || p.photo)
                  ? <img src={p.cardPhoto || p.photo} alt="" loading="lazy" />
                  : <LandPlaceholder theme={p.theme} seed={i + 1} label="" />}
                {p.cardVideo && <HoverVideo sources={[
                  { src: p.cardVideo.mp4, type: "video/mp4" },
                  { src: p.cardVideo.webm, type: "video/webm" },
                ]} />}
              </div>
              <div className="pc-scrim" />

              <span className={`pc-tag ${p.statusKind === "selling" ? "is-selling" : "is-progress"}`}>
                <span className="dot" />{p.statusLabel}
              </span>
              <div className="pc-glass">
                <div className="pc-id">
                  <div className="pc-name"><ProjectName name={p.name} /></div>
                  <div className="pc-loc">{p.loc}</div>
                </div>
                {/* Buy now opens on hover (client, 3 Nov); the plot figures
                    that sat here went. Decorative: the card is the link. */}
                <div className="pc-cta"><BuyNow className="buy-sm" /></div>
              </div>
            </a>
          ))}
          <a href="projects.html" className="pc pc-all reveal"
            style={{ "--reveal-delay": `${120 + visible.length * 90}ms` }}
            aria-label="Explore all projects">
            <span className="pc-all-glass">
              <span className="pc-all-fill" aria-hidden="true" />
              <span className="pc-all-coin" aria-hidden="true"><ArrowIcon size={14} /></span>
              <span className="pc-all-body">
                <span className="pc-all-eyebrow">{PROJECTS.length} communities · {PROJECTS.reduce((n, x) => n + x.plots, 0)} plots</span>
                <span className="pc-all-title">Explore all projects</span>
              </span>
            </span>
          </a>
        </div>
      </div>
    </section>
  );
}

/* ── Method ───────────────────────────────────────────────────── */
function Method() {
  const steps = [
    { num: "01", title: "See", desc: "Every plot we develop is on the platform: mapped, measured, photographed, and documented before you contact us.", icn: MethodIcons.see },
    { num: "02", title: "Walk", desc: "Visit the land at your own pace. No broker between you and the ground. Bring family. Bring an architect.", icn: MethodIcons.walk },
    { num: "03", title: "Verify", desc: "Patta, encumbrance, title: open to you from day one. Our legal team walks you through every document.", icn: MethodIcons.verify },
    { num: "04", title: "Own", desc: "Registration, handover, and continued support after. We are accountable to the land long after you've bought it.", icn: MethodIcons.own },
  ];
  return (
    <section className="method" id="method" data-screen-label="Method">
      <div className="shell">
        <div className="method-head">
          <div className="reveal">
            <h2>From <em style={{ color: "var(--laterite-gold)" }}>first look</em><br />to final signature.</h2>
          </div>
          <p className="t-lead reveal" style={{ maxWidth: "40ch", "--reveal-delay": "200ms" }}>
            One company, every step. No appointments to see documents,
            and no forms to view the land.
          </p>
        </div>

        <div className="method-grid">
          {steps.map((s, i) => {
            const Icon = s.icn;
            return (
              <div key={s.num} className="method-step reveal"
                style={{ "--reveal-delay": `${120 + i * 110}ms` }}>
                <div className="title">{s.title}</div>
                <p className="desc">{s.desc}</p>
                <div className="icn"><Icon /></div>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

/* ── Register Interest ───────────────────────────────────────────
   The form is forms.jsx's `VisitForm`, the same one every other page
   opens in a modal; with no project passed, it asks the reader to
   choose one. What is left here is the section around it: the headline,
   the lead and the three points, which are home-page framing and do not
   travel into the modal. */
function RegisterInterest({ formRef }) {
  return (
    <section className="register" id="register" ref={formRef} data-screen-label="Register">
      <div className="shell register-grid">
        <div className="register-left">
          <h2 className="t-display register-title reveal-fade" style={{ "--reveal-delay": "150ms" }}>
            One form is<br />
            all it takes.
          </h2>
          <p className="t-lead reveal" style={{ "--reveal-delay": "300ms" }}>
            Tell us which ground you'd like to walk. We'll send the documents,
            the map, and a time. No follow-up calls. You hear from us, in writing,
            within one working day.
          </p>

          <div className="register-points reveal" style={{ "--reveal-delay": "420ms" }}>
            <div className="reg-point">
              <span className="dot"></span>
              <div>
                <div className="rp-k">No middlemen</div>
                <div className="rp-v">You speak directly with our team.</div>
              </div>
            </div>
            <div className="reg-point">
              <span className="dot"></span>
              <div>
                <div className="rp-k">Documents first</div>
                <div className="rp-v">Patta, EC, and survey before the meeting.</div>
              </div>
            </div>
            <div className="reg-point">
              <span className="dot"></span>
              <div>
                <div className="rp-k">One working day</div>
                <div className="rp-v">A written reply, every time.</div>
              </div>
            </div>
          </div>
        </div>

        <VisitForm className="register-form reveal-x"
                   style={{ "--reveal-delay": "240ms" }} />
      </div>
    </section>
  );
}

/* ── Footer ───────────────────────────────────────────────────── */
/* ── App ──────────────────────────────────────────────────────── */
function App() {
  const [activeSection, setActiveSection] = useState(null);
  const launchRef = useRef(null);
  const projectsRef = useRef(null);
  const formRef = useRef(null);
  // The launch cards open the visit form on their own project. `visitOn`
  // outlives `visit` so the modal keeps its project while it fades out.
  const [visit, setVisit] = useState(false);
  const [visitOn, setVisitOn] = useState(null);
  const openVisitOn = React.useCallback((name) => { setVisitOn(name); setVisit(true); }, []);
  const closeVisit = React.useCallback(() => setVisit(false), []);

  const jump = (ref) => {
    if (!ref.current) return;
    const top = ref.current.getBoundingClientRect().top + window.scrollY - 56;
    window.scrollTo({ top, behavior: "smooth" });
  };

  // Track active navigation section based on scroll position
  useEffect(() => {
    const sections = [
      { id: 'projects', selector: '#projects' },
      { id: 'method', selector: '#method' },
      { id: 'register', selector: '#register' },
    ];

    const handleScroll = () => {
      let mostVisibleSection = null;
      let maxVisibleHeight = 0;

      sections.forEach(section => {
        const el = document.querySelector(section.selector);
        if (!el) return;

        const rect = el.getBoundingClientRect();
        const windowHeight = window.innerHeight;

        // Calculate how much of this section is visible in the viewport
        const visibleTop = Math.max(0, rect.top);
        const visibleBottom = Math.min(windowHeight, rect.bottom);
        const visibleHeight = Math.max(0, visibleBottom - visibleTop);

        // Track which section has the most visible area on screen
        if (visibleHeight > maxVisibleHeight) {
          maxVisibleHeight = visibleHeight;
          mostVisibleSection = section.id;
        }
      });

      // Only set active section if a section is meaningfully visible (more than 50px)
      if (maxVisibleHeight > 50) {
        setActiveSection(mostVisibleSection);
      } else {
        setActiveSection(null);
      }
    };

    window.addEventListener('scroll', handleScroll);
    handleScroll(); // Call once on mount
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  // Reveal on scroll — observes .reveal / .reveal-x / .reveal-fade.
  // A MutationObserver picks up elements rendered after mount (e.g. when
  // the portfolio filter swaps cards) and starts observing those too.
  useEffect(() => {
    const SEL = ".reveal, .reveal-x, .reveal-fade";
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) {
          e.target.classList.add("in");
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });

    const observe = (root) => {
      const els = root.matches?.(SEL) ? [root] : [];
      root.querySelectorAll?.(SEL).forEach((el) => els.push(el));
      els.forEach((el) => {
        if (!el.classList.contains("in")) io.observe(el);
      });
    };
    observe(document.body);

    const mo = new MutationObserver((muts) => {
      muts.forEach((m) => m.addedNodes.forEach((n) => {
        if (n.nodeType === 1) observe(n);
      }));
    });
    mo.observe(document.body, { childList: true, subtree: true });

    return () => { io.disconnect(); mo.disconnect(); };
  }, []);

  return (
    <>
      <TopBar home activeSection={activeSection} onSchedule={() => jump(formRef)} />
      <main>
        <Hero />
        <Portfolio projectsRef={projectsRef} />
        <Method />
        <NewLaunch launchRef={launchRef} onSchedule={openVisitOn} />
        <RegisterInterest formRef={formRef} />
      </main>
      <Footer home onSchedule={() => jump(formRef)} />
      <VisitModal open={visit} onClose={closeVisit} ground={visitOn} />
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
