/* Yogavibe, components & sections */

/* ============ SEO HELPER ============ */
/* usePageSEO updates document.title, meta description, and canonical URL
   when a React page mounts, and restores the original values on unmount.
   This gives every "page" in the SPA its own SEO surface for search engines
   that follow client-side routing (Google, Bing, increasingly LLM crawlers). */
const SITE_DEFAULTS = {
  title: 'Yogavibe | Yoga Retreats in Morocco (Essaouira & Marrakech) since 2017',
  description: "Yogavibe runs small-group yoga retreats in Essaouira & Marrakech, Morocco, plus custom retreats for companies, friend groups and yoga teachers. Founded 2017 by Charlotte Schoeters. Two daily classes, real food, max 12 guests. Workshops in Antwerp.",
  canonical: 'https://yogavibe.eu/',
  ogTitle: 'Yogavibe | Yoga Retreats in Morocco with Charlotte Schoeters',
  ogDescription: 'Small-group yoga retreats in Essaouira & Marrakech, Morocco. Custom retreats for teams, friends and yoga teachers. Running since 2017.',
  ogImage: 'https://yogavibe.eu/images/yogavibe-essaouira-ramparts-sunset.png',
};

function setMeta(selector, attribute, value) {
  const el = document.querySelector(selector);
  if (el && value != null) el.setAttribute(attribute, value);
}

function usePageSEO({ title, description, canonical, ogTitle, ogDescription, ogImage, ogType, robots }) {
  React.useEffect(() => {
    if (title) document.title = title;
    if (description) setMeta('meta[name="description"]', 'content', description);
    if (canonical) setMeta('link[rel="canonical"]', 'href', canonical);
    if (ogTitle || title) setMeta('meta[property="og:title"]', 'content', ogTitle || title);
    if (ogDescription || description) setMeta('meta[property="og:description"]', 'content', ogDescription || description);
    if (canonical) setMeta('meta[property="og:url"]', 'content', canonical);
    if (ogImage) setMeta('meta[property="og:image"]', 'content', ogImage);
    if (ogType) setMeta('meta[property="og:type"]', 'content', ogType);
    if (ogTitle || title) setMeta('meta[name="twitter:title"]', 'content', ogTitle || title);
    if (ogDescription || description) setMeta('meta[name="twitter:description"]', 'content', ogDescription || description);
    if (ogImage) setMeta('meta[name="twitter:image"]', 'content', ogImage);
    if (robots) setMeta('meta[name="robots"]', 'content', robots);

    return () => {
      document.title = SITE_DEFAULTS.title;
      setMeta('meta[name="description"]', 'content', SITE_DEFAULTS.description);
      setMeta('link[rel="canonical"]', 'href', SITE_DEFAULTS.canonical);
      setMeta('meta[property="og:title"]', 'content', SITE_DEFAULTS.ogTitle);
      setMeta('meta[property="og:description"]', 'content', SITE_DEFAULTS.ogDescription);
      setMeta('meta[property="og:url"]', 'content', SITE_DEFAULTS.canonical);
      setMeta('meta[property="og:image"]', 'content', SITE_DEFAULTS.ogImage);
      setMeta('meta[property="og:type"]', 'content', 'website');
      setMeta('meta[name="twitter:title"]', 'content', SITE_DEFAULTS.ogTitle);
      setMeta('meta[name="twitter:description"]', 'content', SITE_DEFAULTS.ogDescription);
      setMeta('meta[name="twitter:image"]', 'content', SITE_DEFAULTS.ogImage);
      setMeta('meta[name="robots"]', 'content', 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1');
    };
  }, [title, description, canonical, ogTitle, ogDescription, ogImage, ogType, robots]);
}

/* SEO config per page id, used by <PageSEO page="home" /> on the homepage. */
const PAGE_SEO = {
  home: {
    title: 'Yogavibe | Yoga Retreats in Morocco (Essaouira & Marrakech) since 2017',
    description: "Yogavibe runs small-group yoga retreats in Essaouira & Marrakech, Morocco, plus custom retreats for companies, friend groups and yoga teachers. Founded 2017 by Charlotte Schoeters. Two daily classes, real food, max 12 guests. Workshops in Antwerp.",
    canonical: 'https://yogavibe.eu/',
  },
  retreats: {
    title: 'Yoga Retreats in Morocco — 2026 & 2027 Dates | Yogavibe',
    description: 'All upcoming Yogavibe yoga retreats in Morocco. Essaouira and Marrakech, October 2026 through May 2027. Small groups, max 12, from €1,050. Two daily practices, surf, hammam, real food. Book or register interest.',
    canonical: 'https://yogavibe.eu/retreats',
  },
  'retreat-detail': {
    title: 'From Strength to Stillness — 4-Night Yoga Retreat in Essaouira, Morocco | Oct 2026',
    description: 'A 4-night small-group yoga retreat in Essaouira, Morocco, 7–11 October 2026. Rocket & Vinyasa mornings with Sèlia, Yin & Soundbath evenings with Charlotte. Twin from €1,050, single €1,350. Maximum 12 guests, 8 spots left.',
    canonical: 'https://yogavibe.eu/retreats/from-strength-to-stillness-oct-2026',
    ogType: 'event',
  },
  custom: {
    title: 'Custom Yoga Retreats in Morocco for Companies, Friends & Teachers | Yogavibe',
    description: 'Design your own yoga retreat in Morocco. Custom retreats from €950 per person, all in. Full riad takeover for 8–18 guests. Companies, friend groups, yoga teacher collectives. Charlotte Schoeters handles every detail.',
    canonical: 'https://yogavibe.eu/custom-retreats',
  },
  about: {
    title: 'About Charlotte Schoeters & Yogavibe | Belgian Yoga Instructor since 2015',
    description: 'Charlotte Schoeters, born in Antwerp, certified yoga instructor since 2015 (trained in Costa Rica), former Thomas Cook & TUI fly tour guide. Founder of Yogavibe (2017). Read her story, training, and approach.',
    canonical: 'https://yogavibe.eu/about',
  },
  workshops: {
    title: 'Yoga & Soundbath Workshops in Antwerp | Yogavibe with Charlotte Schoeters',
    description: 'One-day yoga deep dives, breathwork & soundbath workshops in Antwerp. Citta Holistic Haven and Atelier Du Mon. From €75. Hosted by Charlotte Schoeters between Yogavibe Morocco retreats.',
    canonical: 'https://yogavibe.eu/workshops',
  },
  journal: {
    title: 'The Yogavibe Journal | Recipes, Guides & Dispatches from Morocco',
    description: 'Recipes from the riad kitchen, packing guides, reading lists and dispatches from Charlotte Schoeters between Yogavibe yoga retreats in Essaouira and beyond.',
    canonical: 'https://yogavibe.eu/journal',
  },
  reservation: {
    title: 'Reserve Your Spot | Yogavibe',
    description: 'Reserve your spot on a Yogavibe yoga retreat or workshop. Secure online payment via Stripe.',
    canonical: 'https://yogavibe.eu/reservation',
    robots: 'noindex, follow',
  },
  giftcard: {
    title: 'Yogavibe Gift Cards — Give a Yoga Retreat | €50–€500',
    description: 'Yogavibe gift cards from €50 to €500. Digital (free) or physical card by post (+€10). Redeemable on any retreat or workshop. Valid 24 months. Secure Stripe checkout.',
    canonical: 'https://yogavibe.eu/gift-cards',
  },
  terms: {
    title: 'Terms & Conditions | Yogavibe',
    description: 'Yogavibe (SRI Group BV) terms and conditions for yoga retreats and workshops. Booking, cancellation, liability.',
    canonical: 'https://yogavibe.eu/terms',
    robots: 'noindex, follow',
  },
  privacy: {
    title: 'Privacy Policy | Yogavibe',
    description: 'Yogavibe (SRI Group BV) privacy policy. How we handle your data under GDPR. Belgian-registered, no advertising trackers.',
    canonical: 'https://yogavibe.eu/privacy',
    robots: 'noindex, follow',
  },
};

function PageSEO({ page }) {
  usePageSEO(PAGE_SEO[page] || PAGE_SEO.home);
  return null;
}

/* ============ CLIENT-SIDE ROUTING ============ */
/* The SPA's "pages" map to real URLs so search engines, sitemap, and shared
   links all resolve. Vercel rewrites every path to Yogavibe.html, then this
   layer reads the path on mount and syncs the URL when the user navigates.
   Journal article slugs match each post's `id` in JOURNAL_POSTS. */
const PAGE_TO_PATH = {
  home: '/',
  retreats: '/retreats',
  'retreat-detail': '/retreats/from-strength-to-stillness-oct-2026',
  custom: '/custom-retreats',
  about: '/about',
  workshops: '/workshops',
  journal: '/journal',
  giftcard: '/gift-cards',
  reservation: '/reservation',
  terms: '/terms',
  privacy: '/privacy',
};

const PATH_TO_PAGE = Object.fromEntries(
  Object.entries(PAGE_TO_PATH).map(([page, path]) => [path, page])
);

function pathToPage(path) {
  if (!path) return { page: 'home', journalSlug: null };
  const clean = path.replace(/\/$/, '') || '/';
  if (PATH_TO_PAGE[clean]) return { page: PATH_TO_PAGE[clean], journalSlug: null };
  const m = clean.match(/^\/journal\/([\w-]+)$/);
  if (m) return { page: 'journal-article', journalSlug: m[1] };
  return { page: 'home', journalSlug: null };
}

function pageToPath(page, journalSlug) {
  if (page === 'journal-article') return journalSlug ? `/journal/${journalSlug}` : '/journal';
  return PAGE_TO_PATH[page] || '/';
}

/* ============ NAV ============ */
function Nav({ page, setPage, direction, setDirection }) {
  const [scrolled, setScrolled] = React.useState(false);
  const [menuOpen, setMenuOpen] = React.useState(false);
  React.useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 40);
    onScroll();
    window.addEventListener('scroll', onScroll);
    return () => window.removeEventListener('scroll', onScroll);
  }, []);

  // Lock body scroll while the mobile drawer is open.
  React.useEffect(() => {
    if (menuOpen) {
      const prev = document.body.style.overflow;
      document.body.style.overflow = 'hidden';
      return () => { document.body.style.overflow = prev; };
    }
  }, [menuOpen]);

  // Close the drawer if the viewport grows past the mobile breakpoint.
  React.useEffect(() => {
    const mq = window.matchMedia('(min-width: 981px)');
    const handler = (e) => { if (e.matches) setMenuOpen(false); };
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, []);

  const items = [
  { id: 'home', label: 'Home' },
  { id: 'retreats', label: 'Retreats' },
  { id: 'workshops', label: 'Workshops' },
  { id: 'about', label: 'About' },
  { id: 'journal', label: 'Journal' },
  { id: 'giftcard', label: 'Gift cards' }];

  const handleNav = (id) => {
    setPage(id);
    setMenuOpen(false);
  };

  return (
    <>
      <nav className="nav" data-scrolled={scrolled}>
        <div className="nav-inner">
          <a className="nav-logo" onClick={() => handleNav('home')}>
            <LogoMark />
            <span className="nav-wordmark">yogavibe</span>
          </a>
          <ul className="nav-list">
            {items.map((it) =>
            <li key={it.id}>
                <a
                onClick={() => setPage(it.id)}
                data-active={page === it.id}>
                {it.label}</a>
              </li>
            )}
          </ul>
          <button
            className="nav-hamburger"
            type="button"
            aria-label={menuOpen ? 'Close menu' : 'Open menu'}
            aria-expanded={menuOpen}
            aria-controls="mobile-drawer"
            onClick={() => setMenuOpen((v) => !v)}>
            <span className="nav-hamburger-bars" data-open={menuOpen}>
              <span></span><span></span><span></span>
            </span>
          </button>
        </div>
      </nav>
      <div
        id="mobile-drawer"
        className="mobile-drawer"
        data-open={menuOpen}
        aria-hidden={!menuOpen}>
        <div className="mobile-drawer-scrim" onClick={() => setMenuOpen(false)} />
        <div className="mobile-drawer-panel" role="dialog" aria-modal="true" aria-label="Site navigation">
          <ul className="mobile-drawer-list">
            {items.map((it) =>
              <li key={it.id}>
                <a
                  onClick={() => handleNav(it.id)}
                  data-active={page === it.id}>
                  {it.label}
                </a>
              </li>
            )}
          </ul>
          <div className="mobile-drawer-foot">
            <a href={`mailto:${window.YOGAVIBE_CONFIG.contact.email}`}>{window.YOGAVIBE_CONFIG.contact.email}</a>
            <a href={`tel:${window.YOGAVIBE_CONFIG.contact.phoneTel}`}>{window.YOGAVIBE_CONFIG.contact.phone}</a>
            <a href={window.YOGAVIBE_CONFIG.social.instagramUrl} target="_blank" rel="noopener">Instagram</a>
          </div>
        </div>
      </div>
    </>);

}

function LogoMark({ size = 28 }) {
  return (
    <span
      className="logo-mark"
      role="img"
      aria-label="Yogavibe"
      style={{ width: size, height: size }}
    />);

}

/* ============ HERO ============ */
function Hero({ heroLayout, setPage }) {
  if (heroLayout === 'editorial') return <HeroEditorial setPage={setPage} />;
  if (heroLayout === 'fullbleed') return <HeroFullBleed setPage={setPage} />;
  return <HeroSplit setPage={setPage} />;
}

function HeroSplit({ setPage }) {
  return (
    <section className="hero hero-split">
      <div className="container hero-split-grid">
        <div className="hero-split-left">
          <div className="eyebrow">Essaouira · 2026</div>
          <h1 className="h-display">
            Salt air,<br />
            <em>slow</em> mornings,<br />
            and the <em>sea</em>.
          </h1>
          <p className="lead" style={{ marginTop: 32 }}>
            Yogavibe runs small, unhurried retreats in Morocco and Thailand, yoga at sunrise, surf at noon, hammam at dusk. Come as you are.
          </p>
          <div className="row gap-sm" style={{ marginTop: 40, flexWrap: 'wrap' }}>
            <button className="btn btn-primary" onClick={() => setPage('retreat-detail')}>
              <span className="dot"></span> Featured retreat, Oct 2026
            </button>
            <button className="btn btn-ghost" onClick={() => setPage('retreats')}>
              All retreats →
            </button>
          </div>
          <div className="hero-meta">
            <div>
              <div className="caption">Trusted by</div>
              <div className="hero-meta-val">9 years running</div>
            </div>
            <div>
              <div className="caption">Since</div>
              <div className="hero-meta-val">2017</div>
            </div>
            <div>
              <div className="caption">Rating</div>
              <div className="hero-meta-val">4.9 ★ Trustpilot</div>
            </div>
          </div>
        </div>
        <div className="hero-split-right">
          <div className="hero-img-tall">
            <Placeholder coords="31.5°N · 9.7°W" label="Warrior on Essaouira beach" tag="HERO · 02" src="images/yoga-warrior-beach.jpg" pos="center" />
          </div>
          <div className="hero-img-wide">
            <Placeholder coords="ESSAOUIRA / Day 03" label="Sunrise silhouette" tag="HERO · 01" src="images/hero-yoga-sunrise.jpg" pos="center 40%" />
          </div>
          <div className="hero-stamp">
            <div className="stamp-ring">
              <svg viewBox="0 0 200 200" className="stamp-svg">
                <defs>
                  <path id="stampcurve" d="M 100,100 m -78,0 a 78,78 0 1,1 156,0 a 78,78 0 1,1 -156,0" />
                </defs>
                <text fontFamily="JetBrains Mono" fontSize="11" letterSpacing="3" fill="currentColor">
                  <textPath href="#stampcurve">
                    YOGAVIBE · ESTD 2017 · ESSAOUIRA, ANTWERP · YOGAVIBE · 
                  </textPath>
                </text>
              </svg>
              <div className="stamp-center">
                <LogoMark size={36} />
                <div className="caption" style={{ marginTop: 4 }}>est. 2017</div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>);

}

function HeroEditorial({ setPage }) {
  return (
    <section className="hero hero-editorial">
      <div className="container">
        <div className="row between" style={{ marginBottom: 32 }}>
          <div className="caption">Vol. 09, Spring 2026</div>
          <div className="caption">A field guide to slow travel</div>
        </div>
        <h1 className="h-display" style={{ textAlign: 'center', maxWidth: 1200, margin: '0 auto' }}>
          Come for the <em>yoga</em>.<br />
          Stay for the <em>everything else</em>.
        </h1>
        <div className="hero-edit-img">
          <Placeholder coords="31.5°N · 9.7°W" label="Warrior on the beach" tag="COVER · 01" src="images/yoga-warrior-fortress.jpg" pos="center 30%" />
        </div>
        <div className="hero-edit-meta">
          <div>
            <div className="caption">Featuring</div>
            <div className="body" style={{ marginTop: 6 }}>Six days in Essaouira, Morocco</div>
          </div>
          <div>
            <div className="caption">Yoga · Surf · Hammam · Horseback</div>
          </div>
          <div>
            <button className="btn btn-primary" onClick={() => setPage('retreats')}>
              <span className="dot"></span> Read the issue
            </button>
          </div>
        </div>
      </div>
    </section>);

}

function HeroFullBleed({ setPage }) {
  return (
    <section className="hero hero-fullbleed">
      <div className="hero-fb-img">
        <video
          className="hero-fb-video"
          src="images/hero-video.mp4"
          autoPlay
          muted
          loop
          playsInline
          preload="auto"
          poster="images/yoga-warrior-fortress.jpg"
          aria-label="Sunrise yoga on Essaouira beach"
        ></video>
        <div className="hero-fb-tag caption">31.5°N · 9.7°W · MOROCCO</div>
        <div className="hero-fb-scrim"></div>
      </div>
      <div className="container hero-fb-content">
        <div className="eyebrow" style={{ color: 'oklch(from var(--bg) l c h / 0.7)' }}>Essaouira · 2026</div>
        <h1 className="h-display" style={{ color: 'var(--bg)' }}>
          Salt air,<br />
          <em>slow</em> mornings,<br />
          and the <em>sea</em>.
        </h1>
        <p className="lead" style={{ marginTop: 28, color: 'oklch(from var(--bg) l c h / 0.85)', maxWidth: 480 }}>Small, unhurried retreats in Morocco.
Yoga at sunrise, surf at noon, hammam at dusk.
        </p>
        <div className="row gap-sm" style={{ marginTop: 36 }}>
          <button className="btn btn-primary" style={{ background: 'var(--bg)', color: 'var(--ink)', borderColor: 'var(--bg)' }} onClick={() => setPage('retreat-detail')}>
            <span className="dot"></span> Book Oct 2026
          </button>
          <button className="btn btn-ghost" style={{ color: 'var(--bg)', borderColor: 'oklch(from var(--bg) l c h / 0.4)' }} onClick={() => setPage('retreats')}>
            All retreats →
          </button>
        </div>
      </div>
    </section>);

}

/* When deployed on Vercel, route images through /_vercel/image so they get
   resized, format-converted (AVIF/WebP), and cached at the edge. Locally
   (file:// or non-Vercel hosts) fall back to the raw src. */
function vercelImage(src, width, quality = 75) {
  if (!src) return src;
  if (typeof window === 'undefined') return src;
  // Local development → just return the raw image.
  if (window.location.hostname === 'localhost' || window.location.protocol === 'file:') return src;
  const url = src.startsWith('/') ? src : '/' + src;
  return `/_vercel/image?url=${encodeURIComponent(url)}&w=${width}&q=${quality}`;
}

function Placeholder({ coords = ",", label = "", tag = "", dark = false, ratio = null, className = "", src = null, pos = "center", priority = false, group = null, groupImages = null, groupIndex = 0 }) {
  const style = ratio ? { aspectRatio: ratio } : {};

  const handleClick = src ? (e) => {
    e.stopPropagation();
    if (window._openLightbox) {
      const imgs = (group && groupImages) ? groupImages : [{ src, label }];
      const idx = (group && groupImages) ? groupIndex : 0;
      window._openLightbox(imgs, idx);
    }
  } : null;

  if (src) {
    const widths = [400, 800, 1200, 1600];
    const srcSet = widths.map(w => `${vercelImage(src, w)} ${w}w`).join(', ');
    return (
      <div
        className={`photo ${dark ? 'dark' : ''} ${className} photo-clickable`}
        style={style}
        onClick={handleClick}
        role="button"
        tabIndex={0}
        onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleClick(e); } }}
        aria-label={`View ${label} full size`}
      >
        <img
          src={vercelImage(src, 1200)}
          srcSet={srcSet}
          sizes="(max-width: 600px) 100vw, (max-width: 1100px) 50vw, 800px"
          alt={label}
          loading={priority ? 'eager' : 'lazy'}
          decoding="async"
          fetchpriority={priority ? 'high' : 'auto'}
          style={{ objectPosition: pos }}
        />
        <div className="photo-overlay">
          <span className="ph-coords">[ {coords} ]</span>
          <span className="ph-tag">{tag}</span>
        </div>
      </div>);
  }
  return (
    <div className={`placeholder ${dark ? 'dark' : ''} ${className}`} style={style}>
      <div className="row between">
        <span className="ph-coords">[ {coords} ]</span>
        <span className="ph-tag">{tag}</span>
      </div>
      <div className="ph-label">→ {label}</div>
    </div>);
}

/* ============ LIGHTBOX ============ */
function LightboxRoot() {
  const [state, setState] = React.useState({ open: false, images: [], index: 0 });

  React.useEffect(() => {
    window._openLightbox = (images, index = 0) => {
      setState({ open: true, images, index });
      document.body.style.overflow = 'hidden';
    };
    return () => { delete window._openLightbox; };
  }, []);

  const close = React.useCallback(() => {
    setState(s => ({ ...s, open: false }));
    document.body.style.overflow = '';
  }, []);

  const prev = React.useCallback(() => setState(s => ({
    ...s, index: (s.index - 1 + s.images.length) % s.images.length
  })), []);

  const next = React.useCallback(() => setState(s => ({
    ...s, index: (s.index + 1) % s.images.length
  })), []);

  React.useEffect(() => {
    if (!state.open) return;
    const onKey = (e) => {
      if (e.key === 'Escape') close();
      if (e.key === 'ArrowLeft') prev();
      if (e.key === 'ArrowRight') next();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [state.open, close, prev, next]);

  const touchStart = React.useRef(null);
  const onTouchStart = (e) => { touchStart.current = e.touches[0].clientX; };
  const onTouchEnd = (e) => {
    if (touchStart.current === null) return;
    const diff = e.changedTouches[0].clientX - touchStart.current;
    if (Math.abs(diff) > 50) { diff > 0 ? prev() : next(); }
    touchStart.current = null;
  };

  if (!state.open) return null;
  const img = state.images[state.index];
  if (!img) return null;
  const hasMultiple = state.images.length > 1;

  return (
    <div className="lightbox" onClick={close} onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} role="dialog" aria-modal="true" aria-label="Image viewer">
      <button className="lightbox-close" onClick={close} aria-label="Close">✕</button>
      {hasMultiple && (
        <>
          <button className="lightbox-prev" onClick={(e) => { e.stopPropagation(); prev(); }} aria-label="Previous image">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
          </button>
          <button className="lightbox-next" onClick={(e) => { e.stopPropagation(); next(); }} aria-label="Next image">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
          </button>
        </>
      )}
      <div className="lightbox-inner" onClick={(e) => e.stopPropagation()}>
        <img src={vercelImage(img.src, 1920, 85)} alt={img.label || ''} />
        {img.label && <div className="lightbox-caption">{img.label}</div>}
        {hasMultiple && <div className="lightbox-counter">{state.index + 1} / {state.images.length}</div>}
      </div>
    </div>
  );
}

Object.assign(window, { Nav, Hero, HeroSplit, HeroEditorial, HeroFullBleed, LogoMark, Placeholder, LightboxRoot, usePageSEO, PageSEO, PAGE_SEO, pathToPage, pageToPath, PAGE_TO_PATH });