﻿/* global React */
const { useState: useStateS, useEffect: useEffectS, useRef: useRefS } = React;

const _em  = () => atob('aGVsbG9AanBhenBob3RvZ3JhcGh5LmNvbQ==');
const _eml = () => 'mailto:' + _em();

function Photo({ variant = 'a', tag, aspect, src }) {
  return (
    <div className={'ph variant-' + variant} style={aspect ? { aspectRatio: aspect } : undefined}>
      {src && <img className="ph-img" src={src} alt="" loading="lazy" />}
      {!src && tag && <span className="ph-tag">{tag}</span>}
    </div>
  );
}

function Intro() {
  return (
    <section className="tight" id="about-preview">
      <div className="container intro">
        <div className="left reveal">
          <span className="eyebrow">Hola, I'm Jorge</span>
          <h2>I make <em>real photos and video</em> for the brands, businesses and families I work with on Sydney's Northern Beaches.</h2>
        </div>
        <div className="right reveal" style={{ transitionDelay: '120ms' }}>
          <p className="lead">Most clients come to me because their last shoot looked too staged, or too try-hard, or too much like everyone else's. I shoot lifestyle, which is just a word for letting things actually happen and being ready when they do.</p>
          <p>For brands that means photo and video content your audience trusts and shares. For families it means photos your kids will recognise as themselves. Same approach, different subject.</p>
          <div className="signature">— Jorge</div>
        </div>
      </div>
    </section>
  );
}

const BRAND_LOGOS = [
  { src: 'assets/Photos/Logos/1.webp', alt: 'Surge Bath House' },
  { src: 'assets/Photos/Logos/2.webp', alt: 'Amber Fitouts Custom Vans' },
  { src: 'assets/Photos/Logos/4.webp', alt: 'Ignite by Jenna' },
  { src: 'assets/Photos/Logos/df.webp', alt: 'Bro Chats' },
  { src: 'assets/Photos/Logos/IH.webp', alt: 'Intelligent Healing' },
  { src: 'assets/Photos/Logos/Steyne.webp', alt: 'The Steyne Hotel' },
  { src: 'assets/Photos/Logos/tk.webp', alt: 'TK' },
  { src: 'assets/Photos/Logos/Untitled%20design.webp', alt: 'SoulBreath' },
  { src: 'assets/Photos/Logos/nuno-terra.webp', alt: 'Nuna Terra Ceramics' },
  { src: 'assets/Photos/Logos/waveo-wireless.webp', alt: 'Waveo Wireless' },
  { src: 'assets/Photos/Logos/ozbarefoot.webp', alt: 'OzBarefoot' },
];

function BrandMarquee() {
  const doubled = [...BRAND_LOGOS, ...BRAND_LOGOS, ...BRAND_LOGOS, ...BRAND_LOGOS];
  return (
    <section className="brand-marquee">
      <div className="marquee-head reveal">
        <span className="eyebrow">In good company</span>
        <h2 className="section-title">A few of the brands I've worked with.</h2>
      </div>
      <div className="marquee-track">
        <div className="marquee-inner">
          {doubled.map((logo, i) => (
            <React.Fragment key={i}>
              <div className="marquee-logo">
                <img src={logo.src} alt={logo.alt} className="marquee-logo-img" loading="lazy" />
              </div>
              <span className="marquee-sep" role="presentation" />
            </React.Fragment>
          ))}
        </div>
      </div>
    </section>
  );
}

const REELS = [
  { slug: 'nuna-terra-reel', client: 'Nuna Terra Ceramics', format: 'Event reel · Wine, Cheese & Clay', alt: 'Event reel for Nuna Terra Ceramics showing a Wine, Cheese and Clay pottery workshop' },
  { slug: 'ozbarefoot-reel-2', client: 'OzBarefoot', format: 'In-store explainer', alt: 'In-store explainer video for OzBarefoot barefoot shoes' },
  { slug: 'afternoon-in-manly', client: 'Afternoon in Manly', format: 'Lifestyle reel', alt: 'Lifestyle reel filmed on the beach at Manly' },
  { slug: 'sample-reel', client: 'Café & market', format: 'Venue reel', alt: 'Venue reel showing a café and market space' },
  { slug: 'shequepancito', client: 'Food styling', format: 'Product reel', alt: 'Product reel of styled fresh bread and flowers' },
];

const SoundIcon = ({ on }) => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <path d="M11 5 6 9H3v6h3l5 4V5z" fill="currentColor" stroke="none" />
    {on
      ? <React.Fragment><path d="M15.5 8.5a5 5 0 0 1 0 7" /><path d="M18.5 5.5a9 9 0 0 1 0 13" /></React.Fragment>
      : <React.Fragment><path d="m16 9 5 6" /><path d="m21 9-5 6" /></React.Fragment>}
  </svg>
);

// Playback manager shared by every place reels appear. Video files are only requested once a card
// is near the viewport and only play while on screen. One reel plays at a time, muted until the
// visitor taps sound. `deps` re-binds the observers when the set of cards changes (e.g. a filter).
function useReelPlayer(deps) {
  const rootRef = useRefS(null);
  const [loaded, setLoaded] = useStateS({});
  const [ratios, setRatios] = useStateS({});
  const [hover, setHover] = useStateS(null);
  const [forced, setForced] = useStateS(null);
  const [paused, setPaused] = useStateS({});
  const [soundIdx, setSoundIdx] = useStateS(null);
  const [tabHidden, setTabHidden] = useStateS(false);

  const calm = typeof window !== 'undefined' && (
    (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) ||
    (navigator.connection && navigator.connection.saveData)
  );
  const canHover = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(hover: hover)').matches;

  useEffectS(() => {
    const frames = rootRef.current ? Array.from(rootRef.current.querySelectorAll('.reel-frame[data-i]')) : [];
    setRatios({});
    if (!('IntersectionObserver' in window)) {
      const all = {}; frames.forEach(f => { all[f.dataset.i] = true; }); setLoaded(all);
      return;
    }
    const near = new IntersectionObserver(entries => {
      setLoaded(prev => {
        const n = { ...prev };
        entries.forEach(e => { if (e.isIntersecting) n[e.target.dataset.i] = true; });
        return n;
      });
    }, { rootMargin: '300px 300px 300px 300px' });
    const view = new IntersectionObserver(entries => {
      setRatios(prev => {
        const n = { ...prev };
        entries.forEach(e => { n[e.target.dataset.i] = e.isIntersecting ? e.intersectionRatio : 0; });
        return n;
      });
    }, { threshold: [0, 0.25, 0.6, 0.9] });
    frames.forEach(f => { near.observe(f); view.observe(f); });
    const vis = () => setTabHidden(document.hidden);
    document.addEventListener('visibilitychange', vis);
    return () => { near.disconnect(); view.disconnect(); document.removeEventListener('visibilitychange', vis); };
  }, deps || []);

  // Which reel plays: hovered > tapped > the most visible one (skipped when motion is reduced or data saver is on)
  let auto = null;
  if (!calm) {
    let best = 0.6;
    Object.keys(ratios).sort((a, b) => a - b).forEach(k => { if (ratios[k] > best) { best = ratios[k]; auto = Number(k); } });
  }
  let active = hover !== null && canHover ? hover : (forced !== null ? forced : auto);
  if (active !== null && (paused[active] || !(ratios[active] > 0.2) || tabHidden)) active = null;

  const toggle = i => {
    if (active === i) {
      setPaused(p => ({ ...p, [i]: true }));
      if (forced === i) setForced(null);
    } else {
      setPaused(p => ({ ...p, [i]: false }));
      setForced(i);
    }
  };
  const toggleSound = i => {
    setPaused(p => ({ ...p, [i]: false }));
    setForced(i);
    setSoundIdx(s => (s === i ? null : i));
  };
  return { rootRef, loaded, active, soundIdx, setHover, toggle, toggleSound };
}

function ReelCard({ index, player, caption = true }) {
  const reel = REELS[index];
  const vRef = useRefS(null);
  const [started, setStarted] = useStateS(false);
  const [playing, setPlaying] = useStateS(false);
  const loaded = !!player.loaded[index];
  const active = player.active === index;
  const sound = player.soundIdx === index && active;

  useEffectS(() => {
    const v = vRef.current;
    if (!v) return;
    v.muted = !sound;
    if (active) {
      const p = v.play();
      if (p && p.catch) p.catch(() => {});
    } else {
      v.pause();
    }
  }, [active, sound, loaded]);

  const key = e => {
    if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); player.toggle(index); }
  };

  return (
    <figure className="reel">
      <div
        className={'reel-frame' + (started ? ' is-started' : '') + (playing ? ' is-playing' : '')}
        data-i={index}
        role="button"
        tabIndex={0}
        aria-label={(playing ? 'Pause: ' : 'Play: ') + reel.alt}
        onClick={() => player.toggle(index)}
        onKeyDown={key}
        onMouseEnter={() => player.setHover(index)}
        onMouseLeave={() => player.setHover(null)}
      >
        <img className="reel-poster" src={'assets/reels/' + reel.slug + '.webp'} alt={reel.alt} width="540" height="960" loading="lazy" decoding="async" />
        {loaded && (
          <video
            ref={vRef}
            className="reel-video"
            src={'assets/reels/' + reel.slug + '.mp4'}
            preload="none"
            muted
            loop
            playsInline
            aria-hidden="true"
            tabIndex={-1}
            onPlaying={() => {
              if (!started && window.jpTrack) window.jpTrack('video_play', { video_title: reel.client + ' · ' + reel.format, page_path: location.pathname });
              setStarted(true); setPlaying(true);
            }}
            onPause={() => setPlaying(false)}
          />
        )}
        <span className="reel-badge" aria-hidden="true">Video</span>
        <span className="reel-play" aria-hidden="true"></span>
        <button
          type="button"
          className="reel-sound"
          aria-pressed={sound}
          aria-label={sound ? 'Mute ' + reel.client + ' reel' : 'Turn sound on for ' + reel.client + ' reel'}
          onClick={e => { e.stopPropagation(); if (!sound && window.jpTrack) window.jpTrack('video_unmute', { video_title: reel.client + ' · ' + reel.format }); player.toggleSound(index); }}
        >
          <SoundIcon on={sound} />
        </button>
      </div>
      {caption && (
        <figcaption className="reel-cap">
          <strong>{reel.client}</strong>
          <span>{reel.format}</span>
        </figcaption>
      )}
    </figure>
  );
}

// Still photo card. `ratio` is width / height; when omitted it matches the reel proportions.
function PhotoCard({ photo, caption = true }) {
  return (
    <figure className="reel reel--photo">
      <div className="reel-frame reel-frame--photo" style={photo.ratio ? { aspectRatio: String(photo.ratio) } : undefined}>
        <img className="reel-poster" src={photo.src} alt={photo.alt} loading="lazy" decoding="async" style={photo.pos ? { objectPosition: photo.pos } : undefined} />
        <span className="reel-badge" aria-hidden="true">Photo</span>
      </div>
      {caption && photo.title && (
        <figcaption className="reel-cap">
          <strong>{photo.title}</strong>
          <span>{photo.format}</span>
        </figcaption>
      )}
    </figure>
  );
}

// Homepage brand section: the pitch and one mixed row of video and photo work.
const HOME_WORK = [
  { type: 'video', index: 0 },
  { type: 'photo', src: 'assets/Photos/Brand/ACF-24.webp', alt: 'Lifestyle brand photography of a custom campervan interior', title: 'Brand lifestyle', format: 'Photography', pos: 'center 40%' },
  { type: 'video', index: 1 },
  { type: 'photo', src: 'assets/Photos/Brand/Simonas-13.webp', alt: 'Brand lifestyle photography on the Northern Beaches', title: 'Brand lifestyle', format: 'Photography', pos: 'center 30%' },
  { type: 'video', index: 2 },
];

const HOME_POINTS = ['Short-form reels', 'Demo and product videos', 'Brand stories and owner interviews', 'Event coverage', 'Brand and lifestyle photography'];

function BrandShowcase() {
  const player = useReelPlayer([]);
  return (
    <section id="brand" className="showcase">
      <div className="container showcase-grid">
        <div className="showcase-title reveal">
          <span className="eyebrow">Brand photo &amp; video</span>
          <h2 className="section-title">Content for your website and social media, in photo and video.</h2>
        </div>
        <div className="reels-row" ref={player.rootRef}>
          {HOME_WORK.map((w, i) => w.type === 'video'
            ? <ReelCard key={'v' + w.index} index={w.index} player={player} />
            : <PhotoCard key={'p' + i} photo={w} />
          )}
        </div>
        <div className="showcase-copy reveal">
          <p>One shoot, a full library: stills and footage made together so your website, socials and ads all look like the same brand. For outdoor brands, wellness, food, activewear and small businesses across Sydney.</p>
          <ul className="showcase-points">
            {HOME_POINTS.map(t => <li key={t}>{t}</li>)}
          </ul>
          <div className="showcase-ctas">
            <a href="brand-lifestyle.html" className="btn">See the work <span className="arr">→</span></a>
            <a href="brand-lifestyle.html#enquire" className="btn ghost">Get a quote <span className="arr">→</span></a>
          </div>
        </div>
      </div>
    </section>
  );
}

// Mobile-only bar with the two actions that turn a visit into an enquiry. Appears after the hero
// and tucks away when the enquiry form is on screen.
function MobileCta({ quoteHref = '#enquire' }) {
  const [show, setShow] = useStateS(false);
  const [atForm, setAtForm] = useStateS(false);
  useEffectS(() => {
    const onScroll = () => setShow(window.scrollY > 520);
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    let io;
    const form = document.getElementById('enquire');
    if (form && 'IntersectionObserver' in window) {
      io = new IntersectionObserver(es => setAtForm(es[0].isIntersecting), { threshold: 0.05 });
      io.observe(form);
    }
    return () => { window.removeEventListener('scroll', onScroll); if (io) io.disconnect(); };
  }, []);
  const visible = show && !atForm;
  useEffectS(() => {
    document.body.classList.toggle('has-mobile-cta', visible);
    return () => document.body.classList.remove('has-mobile-cta');
  }, [visible]);
  return (
    <div className={'mobile-cta' + (visible ? ' is-visible' : '')} aria-hidden={!visible}>
      <a href={quoteHref} className="btn" tabIndex={visible ? 0 : -1}>Get a quote</a>
      <a href="tel:+61479039539" className="btn ghost" tabIndex={visible ? 0 : -1}>Call</a>
    </div>
  );
}

const SERVICES = [
  { n: '01', title: 'Short-form reels', body: 'Vertical video made for Instagram, Facebook and TikTok. Filmed and edited to feel like your brand, and to hold attention from the first second.' },
  { n: '02', title: 'Demo and product videos', body: 'Show how your product works or what it is like to use your service. Filmed in real use so customers know what they are getting.' },
  { n: '03', title: 'Brand stories', body: 'Short films with the owner talking about the brand, why it exists and who it is for. Filmed naturally so it sounds like you, not a script.' },
  { n: '04', title: 'Event coverage', body: 'Launches, markets, classes and openings, captured in photo and video so you have content from the day to share afterwards.' },
  { n: '05', title: 'Brand photography', body: 'Lifestyle, product in use and detail photos for your website, shop and ads. Natural, unposed and ready to use.' },
  { n: '06', title: 'Photo and video together', body: 'Stills and footage from the same day and the same brief, so your website, socials and ads all look like one brand.' },
];

function ServicesGrid({ eyebrow = 'What I create', title, sub }) {
  return (
    <section className="cream" id="services">
      <div className="container">
        <div className="services-head reveal">
          <span className="eyebrow">{eyebrow}</span>
          <h2 className="section-title">{title}</h2>
          {sub && <p>{sub}</p>}
        </div>
        <div className="services-grid reveal-stagger">
          {SERVICES.map(s => (
            <div className="service-card" key={s.n}>
              <span className="num">{s.n}</span>
              <h3>{s.title}</h3>
              <p>{s.body}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

const PERSONAL_CATEGORIES = [
  { num: '01', title: 'Families', blurb: 'The way your kids move, play, cling, wander off and find you again.', variant: 'e', href: 'families.html', src: 'assets/Photos/Family/PJNMS-15.webp' },
  { num: '02', title: 'Maternity', blurb: 'Quiet, natural sessions in the weeks before everything changes.', variant: 'd', href: 'maternity.html', src: 'assets/Photos/Maternity/Kavya-11.webp' },
  { num: '03', title: 'Couples & Weddings', blurb: 'Relaxed sessions and full days. The small looks. The bit where nobody is watching.', variant: 'b', href: 'couples-engagements.html', src: 'assets/Photos/Couples%20%26%20Engagements/EmilyRiley-24.webp' },
];

function PersonalPreview() {
  return (
    <section className="cream" id="personal">
      <div className="container">
        <div className="personal-head">
          <div className="copy reveal">
            <span className="eyebrow">Personal sessions</span>
            <h2 className="section-title">Natural moments, real life, Northern Beaches.</h2>
            <p>Families, maternity, couples and weddings. Same warm, natural approach as the brand work.</p>
          </div>
        </div>
        <div className="personal-grid reveal-stagger">
          {PERSONAL_CATEGORIES.map((c) => (
            <a className="personal-card hoverable" href={c.href} key={c.num}>
              <Photo variant={c.variant} tag={c.title.toLowerCase()} src={c.src} aspect="3/2" />
              <div className="meta-row">
                <h3>{c.title}</h3>
              </div>
              <p className="blurb">{c.blurb}</p>
            </a>
          ))}
        </div>
      </div>
    </section>
  );
}

const TESTIMONIALS = [
  {
    quote: 'Jorge did an incredible job capturing our maternity shoot. The whole experience was so comfortable and enjoyable.',
    name: 'Sandhya',
    where: 'Maternity · NSW',
  },
  {
    quote: 'Had an amazing experience with Jorge. He was professional, easygoing, and captured incredible shots.',
    name: 'Josh',
    where: 'Couples · NSW',
  },
  {
    quote: 'Jorge has been my chosen photographer to capture the most important moments of my life.',
    name: 'Mirella',
    where: 'Weddings · NSW',
    loyalty: 'Booked 3 times',
  },
];

function Testimonials() {
  return (
    <section className="testimonials" id="words">
      <div className="container">
        <div className="reveal">
          <span className="eyebrow">In their words</span>
          <h2 className="section-title" style={{ maxWidth: '20ch' }}>The kind of trust I'm trying to earn.</h2>
        </div>
        <div className="testi-grid reveal-stagger">
          {TESTIMONIALS.map((t) => (
            <div className="testi" key={t.name}>
              <span className="mark">"</span>
              <blockquote>{t.quote}</blockquote>
              <div className="who">
                <strong>{t.name}</strong>
                {t.where}
                {t.loyalty && <div className="loyalty-pill">{t.loyalty}</div>}
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

function GiftVouchers() {
  return (
    <section className="gift-strip">
      <div className="container">
        <div className="gift-inner reveal">
          <div className="copy">
            <span className="eyebrow">Gift Vouchers</span>
            <h3>Give someone a session they'll actually remember.</h3>
            <p>Know someone expecting, a new family, or a couple marking something big? A session makes a gift that lasts a lot longer than anything you'd wrap.</p>
          </div>
          <a href="contact.html" className="btn">Enquire about a voucher <span className="arr">→</span></a>
        </div>
      </div>
    </section>
  );
}

function FinalCTA() {
  return (
    <section className="final-cta" id="contact">
      <div className="container inner">
        <div className="reveal">
          <span className="eyebrow" style={{ color: 'var(--sand)' }}>Let's make something</span>
          <h2>Tell me about your <em>brand</em>, your <em>occasion</em>, or the <em>weekend</em> you've got coming up.</h2>
          <p>One email is enough. I'll write back within 24 hours. If it sounds like a fit, we'll find a date.</p>
          <div className="actions">
            <a href="brand-lifestyle.html" className="btn" style={{ background: 'var(--sand)', borderColor: 'var(--sand)', color: 'var(--ink)' }}>Brand enquiry <span className="arr">→</span></a>
            <a href="contact.html" className="btn ghost">Personal sessions <span className="arr">→</span></a>
          </div>
        </div>
        <div className="side reveal">
          <div className="row">
            <span className="label">Email</span>
            <span className="val"><a href={_eml()}>{_em()}</a></span>
          </div>
          <div className="row">
            <span className="label">Phone</span>
            <span className="val">0479 039 539</span>
          </div>
        </div>
      </div>
    </section>
  );
}

function Footer() {
  return (
    <footer>
      <div className="container">
        <div className="foot-top">
          <div className="logo-row">
            <img src="assets/logo.webp" alt="JPaz Photography" />
            <p className="blurb">Sydney photo and video content creator making genuine content that shows the real feeling of your brand, your products and your life.</p>
          </div>
          <div>
            <h4>Personal</h4>
            <ul>
              <li><a href="maternity.html">Maternity</a></li>
              <li><a href="newborn.html">Newborn</a></li>
              <li><a href="families.html">Families</a></li>
              <li><a href="couples-engagements.html">Couples</a></li>
              <li><a href="weddings-elopements.html">Weddings</a></li>
            </ul>
          </div>
          <div>
            <h4>Studio</h4>
            <ul>
              <li><a href="brand-lifestyle.html">Brand Photo &amp; Video</a></li>
              <li><a href="about.html">About Jorge</a></li>
              <li><a href="blog.html">Journal</a></li>
              <li><a href="contact.html">Contact</a></li>
              <li><a href="contact.html">Gift Vouchers</a></li>
            </ul>
          </div>
          <div>
            <h4>Elsewhere</h4>
            <ul>
              <li><a href="https://www.instagram.com/jpaz.photography" target="_blank" rel="noopener noreferrer">Instagram</a></li>
              <li><a href="https://jpazphotography.pic-time.com" target="_blank" rel="noopener noreferrer">Client galleries</a></li>
              <li><a href={_eml()}>{_em()}</a></li>
              <li><a href="tel:+61479039539">0479 039 539</a></li>
            </ul>
          </div>
        </div>
        <div className="foot-bottom">
          <span>© 2026 JPaz Photography. All rights reserved.</span>
          <div className="foot-legal">
            <a href="privacy-policy.html">Privacy Policy</a>
            <a href="terms-conditions.html">Terms &amp; Conditions</a>
          </div>
          <span className="ack">I acknowledge the Gayamaygal and Garigal people, the traditional custodians of the land on which I live and work, and pay my respects to their Elders past, present and emerging.</span>
        </div>
      </div>
    </footer>
  );
}

function setupReveals() {
  if (!window.gsap || !window.ScrollTrigger) return;
  const ST = window.ScrollTrigger;
  const targets = document.querySelectorAll('.reveal, .reveal-stagger, .split');
  targets.forEach((el) => {
    if (el.classList.contains('in')) return;
    ST.create({
      trigger: el,
      start: 'top 85%',
      onEnter: () => el.classList.add('in'),
    });
  });
}

window.setupReveals = setupReveals;
window.Intro = Intro;
window.BrandMarquee = BrandMarquee;
window.REELS = REELS;
window.useReelPlayer = useReelPlayer;
window.ReelCard = ReelCard;
window.PhotoCard = PhotoCard;
window.MobileCta = MobileCta;
window.BrandShowcase = BrandShowcase;
window.ServicesGrid = ServicesGrid;
window.PersonalPreview = PersonalPreview;
window.Testimonials = Testimonials;
window.GiftVouchers = GiftVouchers;
window.FinalCTA = FinalCTA;
window.Footer = Footer;
