/* IJWestern — the About page. Step 8 of phase 2 (client, 3 Sep 2026).
   Global: none. Renders into #root.

   Three things, in the order the pointers doc asks for them:

     1. a meaningful intro about the company and what it stands for,
     2. the Palakkad map to the right of it, markers popping up with the
        name of the ground they mark,
     3. the Story section, moved off the home page whole in step 3 and
        parked in story.jsx since. about.html loads that file; this page
        renders <Story /> below the map hero.

   **The map is the client's own.** The first cut of this step drew a
   stylised district by hand and the client's answer, 3 Sep, was that it
   did not look like Palakkad. He sent a Google Maps view of the district
   with the five plots marked in red (`Palakkad Map Ref.png`) and the same
   view clean (`pgt-map.png`), and asked for the marks to be animated on
   the real thing. So the terrain here is a photograph, the markers are
   HTML over it, and nothing about the geography is drawn by us any more.
   `site-images/palakkad-map.png` is the square crop of that clean view;
   `tools/README.md` records how it was made and how to redo it.

   Every project fact on this page is read from PROJECT_DATA. The totals
   in the intro are counted, not written, so they cannot drift from the
   five project pages. */

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

const AB_PROJECTS = Object.entries(PROJECT_DATA)
  .map(([slug, p]) => ({ slug, ...p }))
  .sort((a, b) => a.number.localeCompare(b.number));

/* ── Where the five communities sit ─────────────────────────────────
   Fractions of the map image, x then y, read straight off the client's
   marked-up reference: the red blobs were found by colour, and the
   reference was registered against the clean view by phase correlation
   (a flat 13px, 2px offset between the two screenshots), so a marker
   lands on the same pixel of the same road he put it on.

   Which way each name pops is `side`, and it is chosen so the widest
   tag still has room inside the frame at 360px. Only one tag is up at
   a time, so they cannot collide with each other; the frame edge is
   the only thing to stay clear of.

   Two of these came from the client naming his own marks and are worth
   re-reading if a name ever looks wrong on the page: the mark **on the
   966 shield** is Kalpathy (Ratham Gardens) and the one just east of it
   is Chandranagar (Blue Hills Phase 2). */
const MAP_PINS = {
  "blue-hills-phase-2": { x: 0.5959, y: 0.3778, side: "right" }, // Chandranagar
  "ratham-gardens":     { x: 0.5344, y: 0.3623, side: "left"  }, // Kalpathy, on the 966
  "star-gardens":       { x: 0.8313, y: 0.7967, side: "left"  }, // Vandithavalam
  "dream-gardens":      { x: 0.1691, y: 0.8600, side: "right" }, // Thrippalur, by Alathur
  "rose-gardens":       { x: 0.7302, y: 0.3833, side: "left"  }, // Puthussery, by Kanjikode
};

const MAP_SRC = "site-images/palakkad-map.png";
const pct = (v) => v * 100 + "%";

/* ── Hooks ──────────────────────────────────────────────────────── */

/* The site's own reveal system, as projects.jsx carries it. */
function useReveals() {
  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 -6% 0px" }
    );
    document.querySelectorAll(SEL).forEach((el) => {
      if (!el.classList.contains("in")) io.observe(el);
    });
    return () => io.disconnect();
  }, []);
}

function useReducedMotion() {
  const [reduced, setReduced] = useState(
    () => !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches)
  );
  useEffect(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    const on = (e) => setReduced(e.matches);
    mq.addEventListener("change", on);
    return () => mq.removeEventListener("change", on);
  }, []);
  return reduced;
}

/* True on a device that can actually hover. The tilt is a pointer
   effect; on a touch screen there is no pointer to follow and the
   listener would only fire on tap, which reads as the map twitching. */
function useFinePointer() {
  const [fine, setFine] = useState(
    () => !!(window.matchMedia && window.matchMedia("(hover: hover) and (pointer: fine)").matches)
  );
  useEffect(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia("(hover: hover) and (pointer: fine)");
    const on = (e) => setFine(e.matches);
    mq.addEventListener("change", on);
    return () => mq.removeEventListener("change", on);
  }, []);
  return fine;
}

/* ── The map ────────────────────────────────────────────────────────
   Markers pulse; the active one pops its name up. The active one
   advances on its own, and hover or focus takes it over for as long as
   the pointer or the focus is inside the frame. Each marker is a link
   to that project, so the map is navigation and not just decoration.

   Under prefers-reduced-motion nothing pulses, nothing advances and
   nothing pops: the frame is simply there with the first name up, and
   hover or Tab moves it. Every name is still reachable, by pointer or
   by Tab. */
function PalakkadMap() {
  const reduced = useReducedMotion();
  const fine = useFinePointer();
  const [active, setActive] = useState(0);
  const [held, setHeld] = useState(false);
  const tiltRef = useRef(null);

  useEffect(() => {
    if (reduced || held) return;
    const t = setInterval(
      () => setActive((a) => (a + 1) % AB_PROJECTS.length), 3400);
    return () => clearInterval(t);
  }, [reduced, held]);

  /* Tilt. Written straight to the node inside a rAF rather than through
     state: this fires on every pointer move, and a re-render per move
     would re-run the five markers' transitions with it. */
  useEffect(() => {
    if (reduced || !fine) return;
    const el = tiltRef.current;
    if (!el) return;
    let raf = null, nx = 0, ny = 0;
    const apply = () => {
      raf = null;
      el.style.transform = `rotateX(${(-ny * 3.4).toFixed(2)}deg) rotateY(${(nx * 4.4).toFixed(2)}deg)`;
    };
    const move = (e) => {
      const r = el.getBoundingClientRect();
      nx = (e.clientX - r.left) / r.width * 2 - 1;
      ny = (e.clientY - r.top) / r.height * 2 - 1;
      if (raf === null) raf = requestAnimationFrame(apply);
    };
    const leave = () => {
      if (raf !== null) { cancelAnimationFrame(raf); raf = null; }
      el.style.transform = "";
    };
    el.addEventListener("pointermove", move);
    el.addEventListener("pointerleave", leave);
    return () => {
      el.removeEventListener("pointermove", move);
      el.removeEventListener("pointerleave", leave);
      if (raf !== null) cancelAnimationFrame(raf);
      el.style.transform = "";
    };
  }, [reduced, fine]);

  const hold = useCallback((i) => { setHeld(true); setActive(i); }, []);
  const release = useCallback(() => setHeld(false), []);

  return (
    <div className="ab-map reveal-fade" style={{ "--reveal-delay": "220ms" }}>
      <div className={`ab-map-stage${reduced ? " is-still" : ""}`}>
        <div className="ab-map-tilt" ref={tiltRef}>
          <img className="ab-map-img" src={MAP_SRC} alt="" />
          {/* A parchment wash, so a Google Maps green sits in a palette
              of parchment and ink instead of on top of it. */}
          <div className="ab-map-wash" aria-hidden="true" />

          <div className="ab-pins" role="group"
               aria-label="IJWestern communities across Palakkad district">
            {AB_PROJECTS.map((p, i) => {
              const pin = MAP_PINS[p.slug];
              if (!pin) return null;
              return (
                <a key={p.slug}
                   className={`ab-pin${i === active ? " is-on" : ""}`}
                   href={`${p.slug}.html`}
                   data-side={pin.side}
                   data-slug={p.slug}
                   style={{ left: pct(pin.x), top: pct(pin.y), "--pin-i": i }}
                   aria-label={`${p.name}, ${p.locality}. View project.`}
                   onMouseEnter={() => hold(i)}
                   onMouseLeave={release}
                   onFocus={() => hold(i)}
                   onBlur={release}>
                  <span className="ab-pin-dot" aria-hidden="true">
                    <span className="ab-pin-ring" />
                    <span className="ab-pin-ring is-late" />
                    <span className="ab-pin-core" />
                  </span>
                  <span className="ab-flag" aria-hidden="true">
                    <span className="ab-flag-stem" />
                    <span className="ab-flag-plate">
                      <span className="ab-flag-num">{p.number}</span>
                      <span className="ab-flag-name"><ProjectName name={p.name} /></span>
                    </span>
                  </span>
                </a>
              );
            })}
          </div>
        </div>
      </div>

      <p className="ab-map-cap">
        Palakkad district. The five grounds, where they actually are.
      </p>
    </div>
  );
}

/* ── Intro ──────────────────────────────────────────────────────────
   The totals are counted off PROJECT_DATA rather than written into the
   copy, so a corrected figure on a project page corrects this sentence
   with it. CLAUDE.md warns that project facts already live in two
   places; this is not going to be a third. */
const AB_COUNT_WORDS = ["No", "One", "Two", "Three", "Four", "Five",
                        "Six", "Seven", "Eight", "Nine", "Ten"];

const AB_TOTALS = (() => {
  const num = (v) => {
    const n = parseFloat(String(v).replace(/[^0-9.]/g, ""));
    return Number.isFinite(n) ? n : 0;
  };
  const fig = (p, k) => (p.figures || []).find((f) => f.k === k);
  return {
    count: AB_PROJECTS.length,
    plots: AB_PROJECTS.reduce((n, p) => n + num(fig(p, "Plots") && fig(p, "Plots").v), 0),
  };
})();

function AboutHero({ onVisit }) {
  const word = (AB_COUNT_WORDS[AB_TOTALS.count] || AB_TOTALS.count).toLowerCase();

  return (
    <section className="ab-hero" data-screen-label="About">
      <div className="shell ab-hero-grid">
        <div className="ab-intro">
          <p className="ab-eyebrow reveal">IJWestern Realtors</p>

          {/* One sentence, two lines, and the break is explicit. The
              headline is set smaller here than on the other pages, which
              is what lets a whole sentence be two lines rather than
              four; a display size that fits four words is not a short
              title, whatever the word count says. */}
          <h1 className="t-display ab-title reveal" style={{ "--reveal-delay": "90ms" }}>
            A piece of Palakkad,<br />a lifetime of <em>belonging</em>.
          </h1>

          <p className="ab-lead reveal" style={{ "--reveal-delay": "180ms" }}>
            {AB_COUNT_WORDS[AB_TOTALS.count] || AB_TOTALS.count} plotted communities so
            far, {AB_TOTALS.plots} plots in all, each of them on
            ground we found, surveyed and laid out before a single plot of it was for
            sale. No broker stands between a buyer and the land, and there is no second
            company to hand you to when a question gets difficult.
          </p>

          <p className="ab-body reveal" style={{ "--reveal-delay": "260ms" }}>
            Survey figures, documents, prices and the ground itself are open from the
            first visit rather than produced at the end. Nobody is hurried, and
            nothing is kept back to be traded for a signature later. The only reason
            to call us should be to say you want it.
          </p>

          <div className="cta-row ab-cta reveal" style={{ "--reveal-delay": "340ms" }}>
            <a href="projects.html" className="btn btn-primary">
              See all {word} grounds<span className="arrow"><ArrowIcon /></span>
            </a>
            <button type="button" onClick={onVisit} className="btn btn-secondary">
              Schedule a visit<span className="arrow"><ArrowIcon /></span>
            </button>
          </div>
        </div>

        <PalakkadMap />
      </div>
    </section>
  );
}

/* ── Page ───────────────────────────────────────────────────────── */
function AboutPage() {
  const [visit, setVisit] = useState(false);
  useReveals();

  return (
    <>
      <TopBar page="about" onSchedule={() => setVisit(true)} />
      <main className="ab-page" id="top">
        <AboutHero onVisit={() => setVisit(true)} />
        {/* story.jsx, parked there by step 3 and loaded by about.html */}
        <Story />
      </main>
      <Footer onSchedule={() => setVisit(true)} />
      <VisitModal open={visit} onClose={() => setVisit(false)} />
    </>
  );
}

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