(function () {
// URL routing — each case study/project/published-work item gets its own
// path (/case-studies/:id etc.) via history pushState, so links to a
// specific piece of work are shareable and independently indexable instead
// of everything living behind in-memory state under a single "/" URL.
const ROUTE_PREFIX = {
  project: "projects",
  caseStudy: "case-studies",
  published: "published-work"
};
const ROUTE_LABEL = {
  project: "Project",
  caseStudy: "Case Study",
  published: "Published Work"
};
const DEFAULT_TITLE = typeof document !== "undefined" ? document.title : "";
function routePath(kind, id) {
  return "/" + ROUTE_PREFIX[kind] + "/" + id;
}

// Section routes (/work, /projects, /about, /contact) mirror the 4 Nav
// links and scroll to a spot on the same page rather than opening a modal.
// Slugs follow the visible Nav label ("Work" -> #published-work), not the
// section's own DOM id, since "Work"/"Projects" and the ids
// published-work/work don't line up 1:1 (see the comment in Nav.jsx).
const SECTION_SLUGS = {
  work: "published-work",
  projects: "work",
  about: "about",
  contact: "contact"
};
const SECTION_TITLE = {
  work: "Work",
  projects: "Projects",
  about: "About",
  contact: "Contact"
};
const SECTION_ID_TO_SLUG = Object.fromEntries(Object.entries(SECTION_SLUGS).map(([slug, id]) => [id, slug]));

// Shared by the initial-load section scroll, popstate, and the hash-click
// delegate below — scrolls #ft-scroll to a section id, same math the
// pre-existing proximity-snap logic uses. Returns false if the id doesn't
// exist (caller decides whether that means "let the browser handle it").
function scrollToId(scroller, id, behavior) {
  let top = 0;
  if (id !== "top") {
    const el = document.getElementById(id);
    if (!el) return false;
    const er = el.getBoundingClientRect();
    const sr = scroller.getBoundingClientRect();
    top = er.top - sr.top + scroller.scrollTop - window.FT_NAV_OFFSET;
  }
  window.FT_SUPPRESS_SNAP_UNTIL = performance.now() + 1500;
  scroller.scrollTo({
    top: Math.max(0, top),
    behavior
  });
  return true;
}
function resolveRoute(pathname) {
  const parts = pathname.replace(/^\/+|\/+$/g, "").split("/");
  if (parts.length === 1 && SECTION_SLUGS[parts[0]]) {
    return {
      kind: "section",
      slug: parts[0],
      sectionId: SECTION_SLUGS[parts[0]]
    };
  }
  if (parts.length !== 2) return null;
  const [prefix, id] = parts;
  const kind = Object.keys(ROUTE_PREFIX).find(k => ROUTE_PREFIX[k] === prefix);
  if (!kind) return null;
  const list = kind === "project" ? window.FT_PROJECTS : kind === "caseStudy" ? window.FT_CASE_STUDIES : window.FT_PUBLISHED_WORK;
  const item = (list || []).find(x => x.id === id);
  return item ? {
    kind,
    item
  } : null;
}
function titleFor(route) {
  if (!route) return DEFAULT_TITLE;
  if (route.kind === "section") return SECTION_TITLE[route.slug] + " — Smit Waghela";
  return route.item.title + " — " + ROUTE_LABEL[route.kind] + " — Smit Waghela";
}

// FireTail portfolio — App shell
function App() {
  const reduced = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  const initialRoute = React.useMemo(() => typeof window !== "undefined" ? resolveRoute(window.location.pathname) : null, []);
  const [active, setActive] = React.useState(initialRoute && initialRoute.kind === "project" ? initialRoute.item : null);
  const [activeCaseStudy, setActiveCaseStudy] = React.useState(initialRoute && initialRoute.kind === "caseStudy" ? initialRoute.item : null);
  const [activePublished, setActivePublished] = React.useState(initialRoute && initialRoute.kind === "published" ? initialRoute.item : null);
  const [mode, toggleTheme] = useThemeMode();
  const applyRoute = React.useCallback(route => {
    setActive(route && route.kind === "project" ? route.item : null);
    setActiveCaseStudy(route && route.kind === "caseStudy" ? route.item : null);
    setActivePublished(route && route.kind === "published" ? route.item : null);
    document.title = titleFor(route);
  }, []);
  React.useEffect(() => {
    document.title = titleFor(initialRoute);
    if (initialRoute && initialRoute.kind === "section") {
      const scroller = document.querySelector("#ft-scroll");
      if (scroller) scrollToId(scroller, initialRoute.sectionId, "auto");
    }
    const onPopState = () => {
      const route = resolveRoute(window.location.pathname);
      applyRoute(route);
      if (route && route.kind === "section") {
        const scroller = document.querySelector("#ft-scroll");
        if (scroller) scrollToId(scroller, route.sectionId, "auto");
      }
    };
    window.addEventListener("popstate", onPopState);
    return () => window.removeEventListener("popstate", onPopState);
  }, [applyRoute, initialRoute]);
  const openProject = React.useCallback(project => {
    window.history.pushState({
      ftInApp: true
    }, "", routePath("project", project.id));
    applyRoute({
      kind: "project",
      item: project
    });
  }, [applyRoute]);
  const openCaseStudy = React.useCallback(id => {
    const cs = (window.FT_CASE_STUDIES || []).find(c => c.id === id);
    if (!cs) return;
    window.history.pushState({
      ftInApp: true
    }, "", routePath("caseStudy", id));
    applyRoute({
      kind: "caseStudy",
      item: cs
    });
  }, [applyRoute]);
  const openPublished = React.useCallback(item => {
    window.history.pushState({
      ftInApp: true
    }, "", routePath("published", item.id));
    applyRoute({
      kind: "published",
      item
    });
  }, [applyRoute]);

  // Close buttons (and the Nav "home" link) route back to "/". When the
  // current entry was reached via an in-app open (the ftInApp marker below),
  // go back one history entry instead — so a drill-down (e.g. published work
  // -> its case study) lands back on the parent view rather than the
  // homepage. A route with no marker means this tab loaded straight into it
  // (a shared/direct link) — history.back() there would leave the site
  // entirely (e.g. back to a Google results page), so push "/" instead.
  const goHome = React.useCallback(() => {
    if (window.history.state && window.history.state.ftInApp) {
      window.history.back();
    } else {
      window.history.pushState({}, "", "/");
      applyRoute(null);
    }
  }, [applyRoute]);
  React.useEffect(() => {
    const modalOpen = !!(active || activeCaseStudy || activePublished);
    document.body.style.overflow = modalOpen ? "hidden" : "";
    document.body.classList.toggle("ft-modal-open", modalOpen);
  }, [active, activeCaseStudy, activePublished]);

  // Scroll-reveal
  React.useEffect(() => {
    if (reduced) return;
    const els = Array.from(document.querySelectorAll("[data-reveal]"));
    const io = new IntersectionObserver(entries => {
      entries.forEach(en => {
        if (en.isIntersecting) {
          en.target.classList.add("ft-in");
          io.unobserve(en.target);
        }
      });
    }, {
      threshold: 0.12,
      rootMargin: "0px 0px -8% 0px"
    });
    els.forEach(el => io.observe(el));
    return () => io.disconnect();
  }, []);

  // Smooth in-page scrolling — scrolling happens inside #ft-scroll, not window,
  // so native hash-anchor jumps misbehave. Delegate a[href^="#"] clicks (Hero
  // CTAs, the logo, "Open to work") and a[href^="/"] clicks that match one of
  // the 4 section slugs (the Nav links) — both end up scrolling to the same
  // section ids, and either form also updates the URL when it has a mapped
  // slug, so a section is shareable/bookmarkable the same way case studies are.
  React.useEffect(() => {
    const scroller = document.querySelector("#ft-scroll");
    if (!scroller) return;
    const onClick = e => {
      const a = e.target.closest && e.target.closest('a[href^="#"], a[href^="/"]');
      if (!a) return;
      const href = a.getAttribute("href") || "";
      let id = null;
      if (href.startsWith("#")) {
        if (href === "#") return;
        id = href.slice(1);
      } else {
        const pathSlug = href.slice(1);
        if (!SECTION_SLUGS[pathSlug]) return; // not a section link (e.g. Resume) — let it navigate normally
        id = SECTION_SLUGS[pathSlug];
      }
      if (id !== "top" && !document.getElementById(id)) return;
      e.preventDefault();
      scrollToId(scroller, id, reduced ? "auto" : "smooth");
      const targetSlug = SECTION_ID_TO_SLUG[id];
      if (targetSlug && window.location.pathname !== "/" + targetSlug) {
        window.history.pushState({}, "", "/" + targetSlug);
        document.title = titleFor({
          kind: "section",
          slug: targetSlug,
          sectionId: id
        });
      }
    };
    scroller.addEventListener("click", onClick);
    return () => scroller.removeEventListener("click", onClick);
  }, [reduced]);

  // Proximity scroll-snap — JS-based for slow, smooth snapping
  React.useEffect(() => {
    if (reduced) return;
    const scroller = document.querySelector("#ft-scroll");
    if (!scroller) return;
    const SNAP_IDS = ["top", "published-work", "professional-work", "work", "about", "contact"];
    const DEBOUNCE = 1250;
    const DURATION = 3000;
    const PROXIMITY = 0.35; // snap if within 35% of viewport
    let timer = 0;
    let animating = false;
    let raf = 0;
    // Scroll position where the current debounced gesture started — lets
    // onScrollEnd tell "scrolled INTO the snap zone from outside" (should
    // correct) apart from "was already resting inside it" (e.g. reading
    // cards near a section's top; shouldn't get yanked to the exact edge).
    let gestureStartY = null;
    function easeInOutCubic(t) {
      return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
    }
    function cancelSnap() {
      cancelAnimationFrame(raf);
      animating = false;
      clearTimeout(timer);
      timer = setTimeout(onScrollEnd, DEBOUNCE);
    }
    function smoothScroll(target) {
      const start = scroller.scrollTop;
      const delta = target - start;
      if (Math.abs(delta) < 2) return;
      animating = true;
      let t0 = 0;
      const step = ts => {
        // A deliberate click-driven scroll started after this animation did —
        // yield to it immediately rather than fighting over scrollTop.
        if (performance.now() < window.FT_SUPPRESS_SNAP_UNTIL || window.FT_CARD_HOVERED) {
          animating = false;
          return;
        }
        if (!t0) t0 = ts;
        const elapsed = ts - t0;
        const progress = Math.min(elapsed / DURATION, 1);
        scroller.scrollTop = start + delta * easeInOutCubic(progress);
        if (progress < 1) {
          raf = requestAnimationFrame(step);
        } else {
          animating = false;
        }
      };
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(step);
    }
    function getSnapTargets() {
      const sr = scroller.getBoundingClientRect();
      return SNAP_IDS.map(id => {
        if (id === "top") return 0;
        const el = document.getElementById(id);
        if (!el) return null;
        return el.getBoundingClientRect().top - sr.top + scroller.scrollTop - window.FT_NAV_OFFSET;
      }).filter(v => v !== null);
    }
    function onScrollEnd() {
      if (animating || performance.now() < window.FT_SUPPRESS_SNAP_UNTIL || window.FT_CARD_HOVERED) {
        gestureStartY = null;
        return;
      }
      const y = scroller.scrollTop;
      const vh = scroller.clientHeight;
      const startY = gestureStartY != null ? gestureStartY : y;
      gestureStartY = null;

      // Any scroll away from the very top always advances into the first section,
      // full stop — with Hero now a full viewport tall, a single small scroll input
      // often isn't within the generic 35% proximity of that (far away) target,
      // leaving the page stranded mid-Hero instead of snapping. Reference point is
      // the section's own top edge (where its separator line renders), same as the
      // generic snap targets below use.
      const firstEl = document.getElementById("published-work");
      if (firstEl) {
        const sr = scroller.getBoundingClientRect();
        const firstTarget = firstEl.getBoundingClientRect().top - sr.top + scroller.scrollTop - window.FT_NAV_OFFSET;
        if (y > 0 && y < firstTarget) {
          smoothScroll(firstTarget);
          return;
        }
      }
      const threshold = vh * PROXIMITY;
      const targets = getSnapTargets();
      let nearest = null;
      let nearestDist = Infinity;
      for (const t of targets) {
        const d = Math.abs(y - t);
        if (d < nearestDist) {
          nearestDist = d;
          nearest = t;
        }
      }
      if (nearest === null) return;
      // Only correct if this gesture approached the target from outside the
      // snap zone. If it was already inside the zone when the gesture
      // started (e.g. slowly scrolling through cards near a section's top),
      // resting there shouldn't get yanked back to the exact edge.
      const startDist = Math.abs(startY - nearest);
      if (nearestDist > 1 && nearestDist < threshold && startDist >= threshold) {
        smoothScroll(Math.max(0, nearest));
      }
    }
    const onScroll = () => {
      if (animating || performance.now() < window.FT_SUPPRESS_SNAP_UNTIL || window.FT_CARD_HOVERED) return;
      if (gestureStartY === null) gestureStartY = scroller.scrollTop;
      clearTimeout(timer);
      timer = setTimeout(onScrollEnd, DEBOUNCE);
    };
    const onUserInput = () => {
      if (animating) cancelSnap();
    };
    scroller.addEventListener("scroll", onScroll, {
      passive: true
    });
    scroller.addEventListener("wheel", onUserInput, {
      passive: true
    });
    scroller.addEventListener("touchstart", onUserInput, {
      passive: true
    });
    scroller.addEventListener("pointerdown", onUserInput, {
      passive: true
    });
    scroller.addEventListener("keydown", onUserInput, {
      passive: true
    });
    return () => {
      scroller.removeEventListener("scroll", onScroll);
      scroller.removeEventListener("wheel", onUserInput);
      scroller.removeEventListener("touchstart", onUserInput);
      scroller.removeEventListener("pointerdown", onUserInput);
      scroller.removeEventListener("keydown", onUserInput);
      clearTimeout(timer);
      cancelAnimationFrame(raf);
    };
  }, [reduced]);

  // Keep the URL/title in sync with whichever section is actually in view.
  // Nav clicks and the hash-click delegate above already push the URL when
  // the user clicks a link; this covers free scrolling (wheel/drag/keyboard)
  // — without it, scrolling away from a clicked section (e.g. back up to
  // Hero) leaves the address bar showing a section that's no longer on screen.
  React.useEffect(() => {
    if (active || activeCaseStudy || activePublished) return; // a modal owns the URL while open
    const scroller = document.querySelector("#ft-scroll");
    if (!scroller) return;
    const SYNC_IDS = ["published-work", "work", "about", "contact"]; // document order; "top"/Hero is the implicit default (currentId stays null)
    let timer = 0;
    function sync() {
      const sr = scroller.getBoundingClientRect();
      let currentId = null;
      for (const id of SYNC_IDS) {
        const el = document.getElementById(id);
        if (!el) continue;
        const top = el.getBoundingClientRect().top - sr.top + scroller.scrollTop - window.FT_NAV_OFFSET;
        if (scroller.scrollTop >= top - 4) currentId = id;
      }
      const slug = currentId ? SECTION_ID_TO_SLUG[currentId] : null;
      const targetPath = slug ? "/" + slug : "/";
      if (window.location.pathname === targetPath) return;
      // Preserve whatever history.state (e.g. the ftInApp marker) this entry
      // already carries — replaceState only changes the URL, not the marker
      // goHome() relies on to decide back() vs. pushState('/').
      window.history.replaceState(window.history.state, "", targetPath);
      document.title = titleFor(slug ? {
        kind: "section",
        slug,
        sectionId: currentId
      } : null);
    }
    const onScroll = () => {
      clearTimeout(timer);
      timer = setTimeout(sync, 200);
    };
    scroller.addEventListener("scroll", onScroll, {
      passive: true
    });
    sync(); // catch up immediately, e.g. right after a modal closes back onto a mid-scroll page
    return () => {
      scroller.removeEventListener("scroll", onScroll);
      clearTimeout(timer);
    };
  }, [active, activeCaseStudy, activePublished]);
  const blob = {
    position: "absolute",
    left: 0,
    width: "100%",
    height: "60vh",
    pointerEvents: "none"
  };
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
    id: "ft-scroll",
    style: {
      height: "100vh",
      overflowY: "auto",
      background: "transparent",
      willChange: "scroll-position"
    }
  }, /*#__PURE__*/React.createElement("div", {
    style: {
      position: "relative"
    }
  }, !reduced && /*#__PURE__*/React.createElement("div", {
    className: "ft-atmos",
    "aria-hidden": "true",
    style: {
      position: "absolute",
      inset: 0,
      overflow: "hidden",
      pointerEvents: "none",
      zIndex: 0
    }
  }, /*#__PURE__*/React.createElement("div", {
    className: "ft-tail",
    style: {
      ...blob,
      top: "5%",
      background: "radial-gradient(50% 60% at 78% 50%, rgba(31,155,255,0.18) 0%, rgba(69,226,210,0.06) 45%, transparent 100%)"
    }
  }), /*#__PURE__*/React.createElement("div", {
    className: "ft-plume",
    style: {
      ...blob,
      top: "21%",
      background: "radial-gradient(45% 55% at 15% 50%, rgba(122,120,236,0.10) 0%, rgba(31,155,255,0.04) 42%, transparent 100%)"
    }
  }), /*#__PURE__*/React.createElement("div", {
    className: "ft-ember",
    style: {
      ...blob,
      top: "37%",
      background: "radial-gradient(40% 50% at 80% 50%, rgba(69,226,210,0.12) 0%, rgba(122,224,255,0.04) 40%, transparent 100%)"
    }
  }), /*#__PURE__*/React.createElement("div", {
    className: "ft-tail",
    style: {
      ...blob,
      top: "53%",
      background: "radial-gradient(45% 50% at 25% 50%, rgba(31,155,255,0.10) 0%, rgba(69,226,210,0.04) 44%, transparent 100%)"
    }
  }), /*#__PURE__*/React.createElement("div", {
    className: "ft-plume",
    style: {
      ...blob,
      top: "69%",
      background: "radial-gradient(50% 55% at 70% 50%, rgba(31,155,255,0.14) 0%, rgba(69,226,210,0.05) 40%, transparent 100%)"
    }
  }), /*#__PURE__*/React.createElement("div", {
    className: "ft-ember",
    style: {
      ...blob,
      top: "85%",
      background: "radial-gradient(40% 45% at 30% 50%, rgba(122,120,236,0.08) 0%, rgba(31,155,255,0.03) 42%, transparent 100%)"
    }
  })), /*#__PURE__*/React.createElement(Nav, {
    onHome: goHome,
    mode: mode,
    onToggleTheme: toggleTheme
  }), /*#__PURE__*/React.createElement("main", null, /*#__PURE__*/React.createElement(Hero, {
    mode: mode
  }), /*#__PURE__*/React.createElement(PublishedWork, {
    onOpen: openPublished
  }), /*#__PURE__*/React.createElement(ProfessionalWork, {
    onOpenCaseStudy: openCaseStudy
  }), /*#__PURE__*/React.createElement(ProjectGrid, {
    onOpen: openProject
  }), /*#__PURE__*/React.createElement(About, null), /*#__PURE__*/React.createElement(Contact, null)), /*#__PURE__*/React.createElement(Footer, null))), /*#__PURE__*/React.createElement(BottomDock, null), /*#__PURE__*/React.createElement(MorphName, null), /*#__PURE__*/React.createElement(CaseStudy, {
    project: active,
    onClose: goHome,
    isDark: mode === "dark"
  }), /*#__PURE__*/React.createElement(CaseStudyDetail, {
    cs: activeCaseStudy,
    onClose: goHome,
    isDark: mode === "dark"
  }), /*#__PURE__*/React.createElement(PublishedWorkDetail, {
    item: activePublished,
    onClose: goHome,
    onOpenCaseStudy: openCaseStudy
  }));
}
ReactDOM.createRoot(document.getElementById("root")).render(/*#__PURE__*/React.createElement(App, null));
})();
